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.0.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 95a7edabb6..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,13 +149,20 @@ - + - + + + + + + + + @@ -197,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 80738334f8..74f18d89b1 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,18 +1,95 @@ -Contributors +Contributors ============ -Andreas Gudian -Christian Schuster -Christophe Labouisse -Dilip Krishnan -Ewald Volkert -Gunnar Morling -Ivo Smid -Michael Pardo -Mustafa Caylak -Paul Strugnell -Remko Plantenga -Sebastian Hasait -Sjaak Derksen -Timo Eckhardt -Tomek Gubala +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 70c4eaed56..0000000000 --- a/core-common/pom.xml +++ /dev/null @@ -1,77 +0,0 @@ - - - - 4.0.0 - - - org.mapstruct - mapstruct-parent - 1.0.0-SNAPSHOT - ../parent/pom.xml - - - mapstruct-common - jar - MapStruct Core Common - - - true - - - - - junit - junit - test - - - org.easytesting - fest-assert - 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 6a62ef64fe..0000000000 --- a/core-common/src/main/java/org/mapstruct/AfterMapping.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright 2012-2015 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 04f95a38f8..0000000000 --- a/core-common/src/main/java/org/mapstruct/BeanMapping.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2012-2015 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 { }; - - /** - * 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 23995323bc..0000000000 --- a/core-common/src/main/java/org/mapstruct/BeforeMapping.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright 2012-2015 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 b58c7ab7c7..0000000000 --- a/core-common/src/main/java/org/mapstruct/CollectionMappingStrategy.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2012-2015 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/DecoratedWith.java b/core-common/src/main/java/org/mapstruct/DecoratedWith.java deleted file mode 100644 index 961eb48e85..0000000000 --- a/core-common/src/main/java/org/mapstruct/DecoratedWith.java +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright 2012-2015 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; - -/** - * Specifies a decorator to be applied to a generated mapper, which e.g. can be used to amend mappings performed by - * generated mapping methods. - *

- * A typical decorator implementation will be an abstract class and only implement/override a subset of the methods of - * the mapper type which it decorates. All methods not implemented or overridden by the decorator will be implemented by - * the code generator by delegating to the generated mapper implementation. - *

- * NOTE: The usage of decorated mappers differs depending on the selected component model. - *

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

- * @Mapper(componentModel = "...")
- * @DecoratedWith(PersonMapperDecorator.class)
- * public interface PersonMapper {
- *
- *     @Mapping(target = "name", ignore = true)
- *     PersonDto personToPersonDto(Person person);
- *
- *     AddressDto addressToAddressDto(Address address); // not touched by the decorator
- * }
- * 
- * - *

1. Component model 'default'

- *

Referencing the original mapper in the decorator

- *

- * If a constructor with a single parameter accepting the type of the decorated mapper is present, a delegate with - * generated implementations of all the mapper methods will be passed to this constructor. A typical implementation will - * store the passed delegate in a field of the decorator and make use of it in the decorator methods: - * - *

- * public abstract class PersonMapperDecorator implements PersonMapper {
- *
- *     private PersonMapper delegate;
- *
- *     public PersonMapperDecorator(PersonMapper delegate) {
- *         this.delegate = delegate;
- *     }
- *
- *     @Override
- *     public PersonDto personToPersonDto(Person person) {
- *         PersonDto dto = delegate.personToPersonDto( person );
- *         dto.setName( person.getFirstName() + " " + person.getLastName() );
- *
- *         return dto;
- *     }
- * }
- * 
- * - *

Using the decorated mapper

- *

- * Nothing special needs to be done. When using {@code Mappers.getMapper( PersonMapper.class )}, the decorator - * is returned, with the injected original mapper. - *

2. Component model 'spring'

- *

Referencing the original mapper in the decorator

- *

- * The generated implementation of the original mapper is annotated with the Spring annotation - * {@code @Qualifier("delegate")}. To autowire that bean in your decorator, add that qualifier annotation as well: - * - *

- * 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;
- *     }
- * }
- * 
- * - *

Using the decorated mapper in the decorator

- *

- * The generated class that extends the decorator is annotated with Spring's {@code @Primary} annotation. To autowire - * the decorated mapper in the application, nothing special needs to be done: - * - *

- * @Autowired
- * private PersonMapper personMapper; // injects the decorator, with the injected original mapper
- * 
- * - *

3. Component model 'jsr330'

- *

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 @Named("fully-qualified-name-of-generated-impl")} - * (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): - * - *

- * 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;
- *     }
- * }
- * 
- * - *

Using the decorated mapper in the decorator

- *

- * 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 {@code @Named} annotation must be added to select the decorator to be - * injected: - * - *

- * @Inject
- * @Named
- * private PersonMapper personMapper; // injects the decorator, with the injected original mapper
- * 
- *

- * - * @author Gunnar Morling - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) -public @interface DecoratedWith { - - /** - * The decorator type. Must be an abstract class that extends or implements the mapper type to which it is applied. - *

- * For component-model {@code default}, the decorator type must either have a default constructor or a constructor - * with a single parameter accepting the type of the decorated mapper. - * - * @return the decorator type - */ - Class value(); -} 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 69f455c00f..0000000000 --- a/core-common/src/main/java/org/mapstruct/InheritConfiguration.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2012-2015 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 69d1d225c7..0000000000 --- a/core-common/src/main/java/org/mapstruct/InheritInverseConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2012-2015 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 1d1ae6e432..0000000000 --- a/core-common/src/main/java/org/mapstruct/IterableMapping.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright 2012-2015 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.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 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 { }; - - /** - * 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 168bf3c834..0000000000 --- a/core-common/src/main/java/org/mapstruct/MapMapping.java +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright 2012-2015 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.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 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 { }; - - - /** - * 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 { }; - - /** - * 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 725f5541db..0000000000 --- a/core-common/src/main/java/org/mapstruct/Mapper.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright 2012-2015 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.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 @Named} 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; -} 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 bb74c3af24..0000000000 --- a/core-common/src/main/java/org/mapstruct/MapperConfig.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright 2012-2015 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.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 @Named} 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; -} 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 7be5cdbe0a..0000000000 --- a/core-common/src/main/java/org/mapstruct/MappingInheritanceStrategy.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2012-2015 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 1d675cd440..0000000000 --- a/core-common/src/main/java/org/mapstruct/MappingTarget.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2012-2015 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/NullValueMappingStrategy.java b/core-common/src/main/java/org/mapstruct/NullValueMappingStrategy.java deleted file mode 100644 index 433677f470..0000000000 --- a/core-common/src/main/java/org/mapstruct/NullValueMappingStrategy.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2012-2015 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/Qualifier.java b/core-common/src/main/java/org/mapstruct/Qualifier.java deleted file mode 100644 index 551ea5636a..0000000000 --- a/core-common/src/main/java/org/mapstruct/Qualifier.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2012-2015 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.SOURCE)
- * public @interface EnglishToGerman {}
- * 
- * - * @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 8b322a068b..0000000000 --- a/core-common/src/main/java/org/mapstruct/ReportingPolicy.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2012-2015 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 68ad4cd603..0000000000 --- a/core-common/src/main/java/org/mapstruct/TargetType.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012-2015 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 ab87cf1e5b..0000000000 --- a/core-common/src/main/java/org/mapstruct/factory/Mappers.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2012-2015 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 org.mapstruct.Mapper; - -import java.util.ServiceLoader; - -/** - * 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 { - - // Check that - // - clazz is an interface - // - the implementation type implements clazz - // - clazz is annotated with @Mapper - // - // Use privileged action - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - - if ( classLoader == null ) { - classLoader = Mappers.class.getClassLoader(); - } - - 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; - } - } - } - - throw new ClassNotFoundException("Cannot find implementation for " + clazz.getName()); - } - } - catch ( Exception 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 fe4a9845c8..0000000000 --- a/core-common/src/main/java/org/mapstruct/factory/package-info.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2012-2015 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 215f425a7d..0000000000 --- a/core-common/src/main/java/org/mapstruct/package-info.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2012-2015 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 c97289c006..0000000000 --- a/core-common/src/main/java/org/mapstruct/util/Experimental.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2012-2015 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 c6df4703e4..0000000000 --- a/core-common/src/test/java/org/mapstruct/factory/MappersTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2012-2015 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.fest.assertions.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 bc72c70a06..0000000000 --- a/core-common/src/test/java/org/mapstruct/test/model/Foo.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2012-2015 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 b30d409762..0000000000 --- a/core-common/src/test/java/org/mapstruct/test/model/FooImpl.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2012-2015 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 836dc03b8e..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -1,22 +1,9 @@ @@ -25,130 +12,18 @@ org.mapstruct mapstruct-parent - 1.0.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.easytesting - fest-assert - 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 - - - - - - - - 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 7c264f2530..0000000000 --- a/core-jdk8/src/main/java/org/mapstruct/Mapping.java +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright 2012-2015 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.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. - * - * @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 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 - *

- * - *

-     * {@code @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 { }; - - /** - * 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 45d1f4f2d0..0000000000 --- a/core-jdk8/src/main/java/org/mapstruct/Mappings.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2012-2015 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/pom.xml b/core/pom.xml index 518ca28ff7..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.0.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -33,24 +20,15 @@ jar MapStruct Core - - copied-common-sources - - - ${project.groupId} - mapstruct-common - true - - - junit - junit + org.junit.jupiter + junit-jupiter test - org.easytesting - fest-assert + org.assertj + assertj-core 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 @@ -140,6 +84,27 @@ + + org.apache.felix + maven-bundle-plugin + + + org.mapstruct + + + + + maven-jar-plugin + + + ${project.build.outputDirectory}/META-INF/MANIFEST.MF + + + + + 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/src/main/java/org/mapstruct/DecoratedWith.java b/core/src/main/java/org/mapstruct/DecoratedWith.java new file mode 100644 index 0000000000..3db27b2a0a --- /dev/null +++ b/core/src/main/java/org/mapstruct/DecoratedWith.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.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Specifies a decorator to be applied to a generated mapper, which e.g. can be used to amend mappings performed by + * generated mapping methods. + *

+ * A typical decorator implementation will be an abstract class and only implement/override a subset of the methods of + * the mapper type which it decorates. All methods not implemented or overridden by the decorator will be implemented by + * the code generator by delegating to the generated mapper implementation. + *

+ * NOTE: The usage of decorated mappers differs depending on the selected component model. + *

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

+ *

Examples

+ *

+ * For the examples below, consider the following mapper declaration: + * + *

+ * @Mapper(componentModel = "...")
+ * @DecoratedWith(PersonMapperDecorator.class)
+ * public interface PersonMapper {
+ *
+ *     @Mapping(target = "name", ignore = true)
+ *     PersonDto personToPersonDto(Person person);
+ *
+ *     AddressDto addressToAddressDto(Address address); // not touched by the decorator
+ * }
+ * 
+ * + *

1. Component model 'default'

+ *

Referencing the original mapper in the decorator

+ *

+ * If a constructor with a single parameter accepting the type of the decorated mapper is present, a delegate with + * generated implementations of all the mapper methods will be passed to this constructor. A typical implementation will + * store the passed delegate in a field of the decorator and make use of it in the decorator methods: + * + *

+ * public abstract class PersonMapperDecorator implements PersonMapper {
+ *
+ *     private PersonMapper delegate;
+ *
+ *     public PersonMapperDecorator(PersonMapper delegate) {
+ *         this.delegate = delegate;
+ *     }
+ *
+ *     @Override
+ *     public PersonDto personToPersonDto(Person person) {
+ *         PersonDto dto = delegate.personToPersonDto( person );
+ *         dto.setName( person.getFirstName() + " " + person.getLastName() );
+ *
+ *         return dto;
+ *     }
+ * }
+ * 
+ * + *

Using the decorated mapper

+ *

+ * Nothing special needs to be done. When using {@code Mappers.getMapper( PersonMapper.class )}, the decorator + * is returned, with the injected original mapper. + *

2. Component model 'spring'

+ *

Referencing the original mapper in the decorator

+ *

+ * The generated implementation of the original mapper is annotated with the Spring annotation + * {@code @org.springframework.beans.factory.annotation.Qualifier("delegate")}. To autowire that bean in your decorator, + * add that qualifier annotation as well: + * + *

+ * public abstract class PersonMapperDecorator implements PersonMapper {
+ *
+ *     @Autowired
+ *     @org.springframework.beans.factory.annotation.Qualifier("delegate")
+ *     private PersonMapper delegate;
+ *
+ *     @Override
+ *     public PersonDto personToPersonDto(Person person) {
+ *         PersonDto dto = delegate.personToPersonDto( person );
+ *         dto.setName( person.getFirstName() + " " + person.getLastName() );
+ *
+ *         return dto;
+ *     }
+ * }
+ * 
+ * + *

Using the decorated mapper in the decorator

+ *

+ * The generated class that extends the decorator is annotated with Spring's {@code @Primary} annotation. To autowire + * the decorated mapper in the application, nothing special needs to be done: + * + *

+ * @Autowired
+ * private PersonMapper personMapper; // injects the decorator, with the injected original mapper
+ * 
+ * + *

3. Component model 'jsr330' or 'jakarta'

+ *

Referencing the original mapper

+ *

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

+ * public abstract class PersonMapperDecorator implements PersonMapper {
+ *
+ *     @Inject
+ *     @javax.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;
+ *     }
+ * }
+ * 
+ * + *

Using the decorated mapper in the decorator

+ *

+ * 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 {@code @javax.inject.Named} annotation must be added to select the + * decorator to be injected: + * + *

+ * @Inject
+ * @javax.inject.Named
+ * private PersonMapper personMapper; // injects the decorator, with the injected original mapper
+ * 
+ * + * @author Gunnar Morling + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.CLASS) +public @interface DecoratedWith { + + /** + * The decorator type. Must be an abstract class that extends or implements the mapper type to which it is applied. + *

+ * For component-model {@code default}, the decorator type must either have a default constructor or a constructor + * with a single parameter accepting the type of the decorated mapper. + * + * @return the decorator type + */ + Class value(); +} 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 5221a67750..c2a6172e67 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -1,45 +1,147 @@ -/** - * Copyright 2012-2015 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.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. + *

+ * + *

+ * 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 { /** @@ -73,17 +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 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. + * A format string as processable by {@link DecimalFormat} if the annotated method maps from a + * {@link Number} to a {@link String} or vice-versa. Will be ignored for all other element types. + *

+ * If the {@link #locale()} is also specified, the number format will be applied in the context of the given locale. + * Otherwise, the system's default locale will be used to process the number format. + * + * @return A decimal format string as processable by {@link DecimalFormat}. + * @see #locale() + */ + String numberFormat() default ""; + + /** + * Specifies the locale to be used when processing {@link #dateFormat()} or {@link #numberFormat()}. + *

+ * The locale should be a plain tag representing the language, such as "en" for English, "de" for German, etc. + *

+ * If no locale is specified, the system's default locale will be used. + * + * @return A string representing the locale to be used when formatting dates or numbers. + */ + String locale() default ""; + + /** + * A constant {@link String} based on which the specified target property is to be set. + *

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

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

- * - *

-     * {@code @Mapping(
+     * 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: *

@@ -110,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 */ @@ -130,11 +317,98 @@ * 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 { }; + /** + * 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. + *

+ * Note that {@link #defaultValue()} usage will also be converted using this qualifier. + * + * @return One or more qualifier name(s) + * @see #qualifiedBy() + * @see Named + */ + 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. * @@ -154,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 45d1f4f2d0..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-2015 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 new file mode 100644 index 0000000000..7ad3726e48 --- /dev/null +++ b/core/src/main/java/org/mapstruct/ValueMapping.java @@ -0,0 +1,124 @@ +/* + * 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 mapping of source constant value to target constant value. + *

+ * Supported mappings are + *

    + *
  1. Enumeration to Enumeration
  2. + *
+ * Example 1: + * + *

+ * public enum OrderType { RETAIL, B2B, C2C, EXTRA, STANDARD, NORMAL }
+ *
+ * public enum ExternalOrderType { RETAIL, B2B, SPECIAL, DEFAULT }
+ *
+ * @ValueMapping(target = "SPECIAL", source = "EXTRA"),
+ * @ValueMapping(target = "DEFAULT", source = "STANDARD"),
+ * @ValueMapping(target = "DEFAULT", source = "NORMAL")
+ * 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      |
+ * +---------------------+----------------------------+
+ * 
+ * + * Example 2: + * + *

+ * @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:
+ * +---------------------+----------------------------+
+ * | OrderType           | ExternalOrderType          |
+ * +---------------------+----------------------------+
+ * | null                | ExternalOrderType.DEFAULT  |
+ * | OrderType.STANDARD  | null                       |
+ * | OrderType.RETAIL    | ExternalOrderType.RETAIL   |
+ * | OrderType.B2B       | ExternalOrderType.B2B      |
+ * | OrderType.NORMAL    | ExternalOrderType.SPECIAL  |
+ * | OrderType.EXTRA     | ExternalOrderType.SPECIAL  |
+ * +---------------------+----------------------------+
+ * 
+ * + * 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, ElementType.ANNOTATION_TYPE}) +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. + *
  5. {@link MappingConstants#THROW_EXCEPTION}
  6. + *
+ * + * @return The target value. + */ + String target(); + +} diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java new file mode 100644 index 0000000000..95d4c7d5ce --- /dev/null +++ b/core/src/main/java/org/mapstruct/ValueMappings.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; + +/** + * 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, 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 d95466a6cb..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.0.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 2616854981..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,12 +42,16 @@ / - target/freemarker-unpacked/META-INF/NOTICE.txt - / + target/freemarker-unpacked/META-INF/LICENSE + etc/freemarker - target/freemarker-unpacked/META-INF/LICENSE.txt - etc/freemarker + ../documentation/target/generated-docs/mapstruct-reference-guide.html + docs/reference/html + + + ../documentation/target/generated-docs/mapstruct-reference-guide.pdf + docs/reference/pdf @@ -86,7 +76,7 @@ - target/site/apidocs + target/reports/apidocs docs/api diff --git a/documentation/pom.xml b/documentation/pom.xml new file mode 100644 index 0000000000..21a2b151bd --- /dev/null +++ b/documentation/pom.xml @@ -0,0 +1,99 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-parent + 1.7.0-SNAPSHOT + ../parent/pom.xml + + + mapstruct-documentation + pom + MapStruct Documentation + + + 1.6.0 + 2.5.1 + 9.2.17.0 + + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + 2.1.0 + + + org.asciidoctor + asciidoctorj-pdf + ${asciidoctorj.pdf.version} + + + org.jruby + jruby-complete + ${jruby.version} + + + org.asciidoctor + asciidoctorj + ${asciidoctorj.version} + + + + mapstruct-reference-guide.asciidoc + + ${project.version} + font + + + + + output-html + generate-resources + + process-asciidoc + + + html + + + + output-pdf + generate-resources + + process-asciidoc + + + pdf + + + + + - + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + 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 new file mode 100644 index 0000000000..1705ed4af1 --- /dev/null +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -0,0 +1,50 @@ += MapStruct {mapstructVersion} Reference Guide +:revdate: {docdate} +:toc: right +: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://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 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: + +include::chapter-1-introduction.asciidoc[] + +include::chapter-2-set-up.asciidoc[] + +include::chapter-3-defining-a-mapper.asciidoc[] + +include::chapter-4-retrieving-a-mapper.asciidoc[] + +include::chapter-5-data-type-conversions.asciidoc[] + +include::chapter-6-mapping-collections.asciidoc[] + +include::chapter-7-mapping-streams.asciidoc[] + +include::chapter-8-mapping-values.asciidoc[] + +include::chapter-9-object-factories.asciidoc[] + +include::chapter-10-advanced-mapping-options.asciidoc[] + +include::chapter-11-reusing-mapping-configurations.asciidoc[] + +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 6967c8a3ba..66af6edf79 100644 --- a/etc/license.txt +++ b/etc/license.txt @@ -1,16 +1,3 @@ - Copyright 2012-2015 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. \ No newline at end of file +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 8ee544d1db..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.0.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -35,25 +22,62 @@ ${project.version} + true + + + + gradle + https://repo.gradle.org/artifactory/libs-releases/ + + true + + + false + + + - junit - junit + org.assertj + assertj-core test - org.easytesting - fest-assert + org.apache.maven.shared + maven-verifier test - org.apache.maven.shared - maven-verifier + 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 34acb534a5..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/CdiTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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/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 701af0465d..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/Java8Test.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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 6eb3873a33..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/JaxbTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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 1588baaf3d..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/Jsr330Test.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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 6607723076..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/NamingStrategyTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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 bedaab2584..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/SimpleTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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 0f442f85da..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/SpringTest.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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/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 826b4384ed..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuite.java +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright 2012-2015 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; - -/** - * 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 - */ - public enum ProcessorType { - /** - * Use an Oracle JDK 1.6 (or 1.6.x) via toolchain support to perform the processing - */ - ORACLE_JAVA_6( "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( "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( "oracle-[1.9,1.10)", "javac", "1.9" ), - - /** - * 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_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 String toolchain; - private String compilerId; - private String sourceTargetVersion; - - private ProcessorType(String toolchain, String compilerId, String sourceTargetVersion) { - this.toolchain = toolchain; - this.compilerId = compilerId; - this.sourceTargetVersion = sourceTargetVersion; - } - - private 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 String getToolchain() { - return toolchain; - } - - /** - * @return the compilerId - */ - public String getCompilerId() { - return compilerId; - } - - /** - * @return the sourceTargetVersion - */ - public String getSourceTargetVersion() { - return sourceTargetVersion; - } - } - - /** - * @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 }; -} 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 494dc9918a..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuiteRunner.java +++ /dev/null @@ -1,297 +0,0 @@ -/** - * Copyright 2012-2015 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.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -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.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner.ProcessorTestCase; - -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; - -/** - * 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"; - - public static final String SYS_PROP_CAN_USE_JDK_9 = "processorIntegrationTest.canUseJdk9"; - - public static final class ProcessorTestCase { - private final String baseDir; - private final ProcessorType processor; - private final boolean ignored; - - public ProcessorTestCase(String baseDir, ProcessorType processor) { - this.baseDir = baseDir; - this.processor = processor; - this.ignored = - ( !TOOLCHAINS_ENABLED && processor.getToolchain() != null ) - || ( processor == ProcessorType.ORACLE_JAVA_9 && !CAN_USE_JDK_9 ); - } - } - - private static final File SPECIFIED_TOOLCHAINS_FILE = getSpecifiedToolchainsFile(); - private static final boolean TOOLCHAINS_ENABLED = toolchainsFileExists(); - private static final boolean CAN_USE_JDK_9 = canUseJdk9(); - - 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" ); - } - - methods = initializeTestCases( suite ); - } - - private List initializeTestCases(ProcessorSuite suite) { - 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 ) ); - } - - 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" ); - - originalOut.println( "executing " + child.processor.name().toLowerCase() ); - - verifier.executeGoals( goals ); - verifier.verifyErrorFreeLog(); - } - finally { - verifier.resetStreams(); - } - } - - 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 ) { - if ( null != SPECIFIED_TOOLCHAINS_FILE ) { - verifier.addCliOption( "--toolchains" ); - verifier.addCliOption( SPECIFIED_TOOLCHAINS_FILE.getPath().replace( '\\', '/' ) ); - } - - String[] parts = child.processor.getToolchain().split( "-" ); - - verifier.addCliOption( "-Dtoolchain-jdk-vendor=" + parts[0] ); - verifier.addCliOption( "-Dtoolchain-jdk-version=" + parts[1] ); - - 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 getSpecifiedToolchainsFile() { - 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; - } - } - - return null; - } - - private static boolean canUseJdk9() { - return Boolean.parseBoolean( System.getProperty( SYS_PROP_CAN_USE_JDK_9, "true" ) ); - } - - private static boolean toolchainsFileExists() { - if ( null != SPECIFIED_TOOLCHAINS_FILE ) { - return SPECIFIED_TOOLCHAINS_FILE.exists(); - } - - String defaultPath = System.getProperty( "user.home" ) + System.getProperty( "file.separator" ) + ".m2"; - return new File( defaultPath, "toolchains.xml" ).exists(); - } - -} 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 2eec5d2727..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 34ac84d2c8..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-2015 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 10e625b341..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-2015 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 cb7bc78777..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-2015 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 57f9724afd..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-2015 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 c88072f246..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-2015 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 4b54d7bee0..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-2015 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 fe00936b88..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-2015 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.cdi; -import static org.fest.assertions.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 new file mode 100644 index 0000000000..670d5cff92 --- /dev/null +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -0,0 +1,134 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + fullFeatureTest + jar + + + x + x + x + x + x + x + x + x + x + x + x + x + + + + ../../../../../processor/src/test/java + + + org.apache.maven.plugins + maven-compiler-plugin + + + **/erroneous/**/*.java + **/*Erroneous*.java + **/*Test.java + **/testutil/**/*.java + **/spi/**/*.java + **/kotlin/**/*.java + ${additionalExclude0} + ${additionalExclude1} + ${additionalExclude2} + ${additionalExclude3} + ${additionalExclude4} + ${additionalExclude5} + ${additionalExclude6} + ${additionalExclude7} + ${additionalExclude8} + ${additionalExclude9} + ${additionalExclude10} + ${additionalExclude11} + ${additionalExclude12} + ${additionalExclude13} + + + + + + + + + com.google.guava + guava + + + javax.inject + javax.inject + + + jakarta.inject + jakarta.inject-api + + + jakarta.enterprise + jakarta.enterprise.cdi-api + + + + + org.springframework + spring-beans + + + org.springframework + spring-context + + + + 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 new file mode 100644 index 0000000000..4f2d7bd9ed --- /dev/null +++ b/integrationtest/src/test/resources/fullFeatureTest/src/test/java/org/mapstruct/itest/simple/AnimalTest.java @@ -0,0 +1,46 @@ +/* + * 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.test.ignore; + +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 + */ +class AnimalTest { + + @Test + void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + + AnimalDto animalDto = AnimalMapper.INSTANCE.animalToDto( animal ); + + assertThat( animalDto ).isNotNull(); + assertThat( animalDto.getName() ).isEqualTo( "Bruno" ); + assertThat( animalDto.getSize() ).isEqualTo( 100 ); + assertThat( animalDto.getAge() ).isNull(); + assertThat( animalDto.getColor() ).isNull(); + } + + @Test + void shouldNotPropagateIgnoredPropertyInReverseMappingWhenSourceAndTargetAreSpecified() { + 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.getColour() ).isNull(); + } +} 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 17339a260e..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 e3cc530c0e..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-2015 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 9ab53c62a8..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-2015 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 d062062c0c..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-2015 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 2cb9f4e06a..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-2015 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 71caf17588..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-2015 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 8628bbbf70..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-2015 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 688d38ffed..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-2015 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 a30dbb83ee..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-2015 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 a1dfef4f5f..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-2015 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 0dd44eb1c0..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,24 +1,10 @@ -/** - * Copyright 2012-2015 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; - public class Target { private Foo foo; private Bar bar; 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 3df627e176..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-2015 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 6997428dc2..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-2015 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 fa82903b1e..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-2015 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 f4f9e1177e..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-2015 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.fest.assertions.Assertions.assertThat; +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 df8544c9bd..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-2015 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.fest.assertions.Assertions.assertThat; +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 b1ed6334f6..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-2015 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.fest.assertions.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 789a71943f..0e69e23e01 100644 --- a/integrationtest/src/test/resources/jaxbTest/pom.xml +++ b/integrationtest/src/test/resources/jaxbTest/pom.xml @@ -1,22 +1,9 @@ \${project.build.resources[0].directory}/schema/ **/test1.xsd + **/underscores.xsd \${project.build.resources[0].directory}/binding @@ -59,7 +47,38 @@ 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 11d661ef48..0000000000 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/JaxbMapper.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2012-2015 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 f03e4f740e..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-2015 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 1116db69f8..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-2015 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; @@ -30,7 +17,6 @@ public class OrderDto { private OrderDetailsDto orderDetails; private ShippingAddressDto shippingAddress; - public Long getOrderNumber() { return orderNumber; } 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 e910323ae9..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-2015 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 83f7d17870..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-2015 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 1871e81797..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-2015 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; @@ -24,7 +11,7 @@ import org.mapstruct.itest.jaxb.xsd.test1.OrderType; import org.mapstruct.itest.jaxb.xsd.test2.OrderStatusType; import org.mapstruct.itest.jaxb.xsd.test2.ShippingAddressType; - +import org.mapstruct.itest.jaxb.xsd.underscores.SubType; /** * @author Sjaak Derksen @@ -32,7 +19,7 @@ @Mapper(uses = { org.mapstruct.itest.jaxb.xsd.test1.ObjectFactory.class, org.mapstruct.itest.jaxb.xsd.test2.ObjectFactory.class, - JaxbMapper.class + org.mapstruct.itest.jaxb.xsd.underscores.ObjectFactory.class }) public interface SourceTargetMapper { @@ -47,6 +34,8 @@ public interface SourceTargetMapper { ShippingAddressDto shippingAddressToDto(ShippingAddressType source); + SubTypeDto subTypeToDto(SubType source); + // target 2 source methods OrderType targetToSource(OrderDto target); @@ -55,4 +44,6 @@ public interface SourceTargetMapper { OrderStatusType dtoToStatus(OrderStatusDto target); ShippingAddressType dtoToShippingAddress(ShippingAddressDto source); + + SubType dtoToSubType(SubTypeDto source); } 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 new file mode 100644 index 0000000000..affa8e3536 --- /dev/null +++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/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.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/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 new file mode 100644 index 0000000000..f07875e8ee --- /dev/null +++ b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/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.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/jaxbTest/src/main/resources/binding/binding.xjb b/integrationtest/src/test/resources/jaxbTest/src/main/resources/binding/binding.xjb index d7e79f6dda..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 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java b/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java index 0695fc3760..d54484334b 100644 --- a/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java +++ b/integrationtest/src/test/resources/jaxbTest/src/test/java/org/mapstruct/itest/jaxb/JaxbBasedMapperTest.java @@ -1,48 +1,35 @@ -/** - * Copyright 2012-2015 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; -import static org.fest.assertions.Assertions.assertThat; - import java.io.ByteArrayOutputStream; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; - import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.itest.jaxb.xsd.test1.ObjectFactory; import org.mapstruct.itest.jaxb.xsd.test1.OrderType; +import org.mapstruct.itest.jaxb.xsd.underscores.SubType; + +import static org.assertj.core.api.Assertions.assertThat; /** * Test for generation of JAXB based mapper implementations. * * @author Sjaak Derksen */ -public class JaxbBasedMapperTest { +class JaxbBasedMapperTest { @Test - public void shouldMapJaxb() throws ParseException, JAXBException { + void shouldMapJaxb() throws ParseException, JAXBException { SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; @@ -91,6 +78,27 @@ public void shouldMapJaxb() throws ParseException, JAXBException { 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 ); diff --git a/integrationtest/src/test/resources/jsr330Test/pom.xml b/integrationtest/src/test/resources/jsr330Test/pom.xml index b641cfd555..e1cf959a90 100644 --- a/integrationtest/src/test/resources/jsr330Test/pom.xml +++ b/integrationtest/src/test/resources/jsr330Test/pom.xml @@ -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 c1ea2d8efd..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-2015 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 8feef2aa62..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-2015 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 cfebaeba56..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-2015 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 9c0c51ec16..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-2015 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 017336375e..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-2015 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 cd3d9810d5..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-2015 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 c79c1e0f83..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-2015 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 503874ce22..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-2015 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,10 @@ import java.text.SimpleDateFormat; import java.util.Date; -import javax.inject.Named; +import jakarta.inject.Named; +import jakarta.inject.Singleton; +@Singleton @Named public class DateMapper { 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 ca84734990..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,35 +1,21 @@ -/** - * Copyright 2012-2015 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.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Test for generation of JSR-330-based Mapper implementations @@ -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 6507b99c5e..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 9462a76a37..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 ef0b8e7b73..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-2015 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; -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.MethodType; +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 @@ -32,34 +18,21 @@ * * @author Gunnar Morling */ -public class CustomAccessorNamingStrategy implements AccessorNamingStrategy { +public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy implements AccessorNamingStrategy { @Override - public MethodType getMethodType(ExecutableElement method) { - if ( isGetterMethod( method ) ) { - return MethodType.GETTER; - } - else if ( isSetterMethod( method ) ) { - return MethodType.SETTER; - } - else if ( isAdderMethod( method ) ) { - return MethodType.ADDER; - } - else { - return MethodType.OTHER; - } - } - - private boolean isGetterMethod(ExecutableElement method) { + public boolean isGetterMethod(ExecutableElement method) { return 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 boolean isAdderMethod(ExecutableElement method) { String methodName = method.getSimpleName().toString(); return methodName.startsWith( "add" ) && methodName.length() > 3; @@ -68,17 +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 ) ); } - @Override - public String getCollectionGetterName(String property) { - return property.substring( 0, 1 ).toUpperCase() + property.substring( 1 ); - } } diff --git a/integrationtest/src/test/resources/namingStrategyTest/usage/pom.xml b/integrationtest/src/test/resources/namingStrategyTest/usage/pom.xml index e4868710a4..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 38977e62b6..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-2015 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 2bbcc939b5..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-2015 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 2f93868b3c..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-2015 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 e44fa70ae7..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-2015 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; -import static org.fest.assertions.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 0e0cc4f3e1..f4cd0b51de 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -1,22 +1,9 @@ ${mapstruct.version} - foobar - - + 6.0.3 @@ -64,7 +49,7 @@ org.eclipse.tycho tycho-compiler-jdt - 0.21.0 + ${org.eclipse.tycho.compiler-jdt.version} @@ -80,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} @@ -148,20 +129,20 @@ ${project.groupId} - \${mapstruct-artifact-id} + mapstruct ${mapstruct.version} provided - junit - junit + org.junit.jupiter + junit-jupiter test - org.easytesting - fest-assert + org.assertj + assertj-core test @@ -176,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 50295a486a..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 197e5ee35b..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-2015 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 ae4257028e..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-2015 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 e3be2c14be..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-2015 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 0cc181acea..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-2015 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 8cf1ff9cb5..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-2015 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 5cd0624d47..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-2015 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 21f6092211..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-2015 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 1e7937971f..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-2015 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 { @@ -80,7 +68,7 @@ public void setSomeType(SomeType someType) { this.someType = someType; } - public String getFormNested() { + public String getFromNested() { return fromNested; } 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 91bbbce835..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-2015 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 cfbf491469..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-2015 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.fest.assertions.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 dbc73325c0..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 f80d98a713..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-2015 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 b179d89b13..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-2015 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 a33ec59d4c..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-2015 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 5a126d5d14..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-2015 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 4d0eb09c56..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-2015 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 7f2a85ecdb..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-2015 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 efa5425636..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-2015 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 b646fff1f5..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-2015 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 596dad7779..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,33 +1,20 @@ -/** - * Copyright 2012-2015 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.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Test for generation of Spring-based Mapper implementations @@ -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 new file mode 100644 index 0000000000..f122047093 --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + + + org.mapstruct.itest + itest-supertypegeneration-aggregator + 1.0.0 + ../pom.xml + + + itest-supertypegeneration-generator + jar + + + + org.junit.jupiter + junit-jupiter + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -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 new file mode 100644 index 0000000000..1147d78083 --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/BaseGenerationProcessor.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.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; +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 base classes. + * + * @author Gunnar Morling + * + */ +@SupportedAnnotationTypes("org.mapstruct.itest.supertypegeneration.GenBase") +public class BaseGenerationProcessor extends AbstractProcessor { + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for (TypeElement annotation : annotations) { + Set annotated = roundEnv.getElementsAnnotatedWith( annotation ); + for (Element element : annotated) { + AnnotationMirror annotationMirror = element.getAnnotationMirrors().get(0); + Integer level = (Integer) annotationMirror.getElementValues().values().iterator().next().getValue(); + + if ( level >= 1 ) { + try { + PackageElement packageElement = processingEnv.getElementUtils().getPackageOf( element ); + Name name = element.getSimpleName(); + String baseName = name.toString() + "Base"; + String baseBaseName = level > 1 ? baseName + "Base" : null; + + JavaFileObject baseGen = processingEnv.getFiler().createSourceFile( packageElement.getQualifiedName() + "." + baseName ); + Writer writer = baseGen.openWriter(); + writer.append( "package " + packageElement.getQualifiedName() + ";\n" ); + writer.append( "\n" ); + writer.append( "import org.mapstruct.itest.supertypegeneration.GenBase;\n" ); + writer.append( "\n" ); + if ( level > 1 ) { + writer.append( "@GenBase( " + (level - 1) + " )\n" ); + writer.append( "public class " + baseName + " extends " + baseBaseName + " {\n" ); + } + else { + writer.append( "public class " + baseName + " {\n" ); + } + writer.append( " private String baseName" + level + ";\n" ); + writer.append( " public String getBaseName" + level + "() { return baseName" + level + "; }\n" ); + writer.append( " public void setBaseName" + level + "(String baseName" + level + ") { this.baseName" + level + " = baseName" + level + ";}\n" ); + writer.append( "}\n" ); + writer.flush(); + writer.close(); + } + catch (IOException e) { + throw new RuntimeException( e ); + } + } + } + } + + return false; + } +} 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 new file mode 100644 index 0000000000..b5bada8d14 --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/supertypegeneration/GenBase.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.supertypegeneration; + +/** + * Triggers generation of base-classes. + * + * @author Gunnar Morling + * + */ +public @interface GenBase { + + int value(); +} 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 new file mode 100644 index 0000000000..ac02411c1c --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/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.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 new file mode 100644 index 0000000000..e522d12b5b --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/pom.xml @@ -0,0 +1,27 @@ + + + + 4.0.0 + + + org.mapstruct + mapstruct-it-parent + 1.0.0 + ../pom.xml + + + org.mapstruct.itest + itest-supertypegeneration-aggregator + pom + + + generator + usage + + diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml new file mode 100644 index 0000000000..4c5e4611f0 --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml @@ -0,0 +1,50 @@ + + + 4.0.0 + + + org.mapstruct.itest + itest-supertypegeneration-aggregator + 1.0.0 + ../pom.xml + + + itest-supertypegeneration-usage + jar + + + + org.junit.jupiter + junit-jupiter + test + + + org.mapstruct.itest + itest-supertypegeneration-generator + 1.0.0 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + -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 new file mode 100644 index 0000000000..09abaf5615 --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/AnotherOrderMapper.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.supertypegeneration.usage; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Gunnar Morling + * + */ +@Mapper +public abstract class AnotherOrderMapper { + + public static final AnotherOrderMapper INSTANCE = Mappers.getMapper( AnotherOrderMapper.class ); + + public abstract OrderDto orderToDto(Order order); +} 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 new file mode 100644 index 0000000000..ae2315bb19 --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/Order.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.supertypegeneration.usage; + +import org.mapstruct.itest.supertypegeneration.GenBase; + +/** + * @author Gunnar Morling + */ +@GenBase(2) +public class Order extends OrderBase { + + private String item; + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } +} 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 new file mode 100644 index 0000000000..f70224b753 --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderDto.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.supertypegeneration.usage; + +import org.mapstruct.itest.supertypegeneration.GenBase; + +/** + * @author Gunnar Morling + */ +@GenBase(2) +public class OrderDto extends OrderDtoBase { + + private String item; + + public String getItem() { + return item; + } + + public void setItem(String item) { + this.item = item; + } +} 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 new file mode 100644 index 0000000000..b3d6325d74 --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/main/java/org/mapstruct/itest/supertypegeneration/usage/OrderMapper.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.supertypegeneration.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/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 new file mode 100644 index 0000000000..d0c76874ae --- /dev/null +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/src/test/java/org/mapstruct/itest/supertypegeneration/usage/GeneratedBasesTest.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.supertypegeneration.usage; + +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 + */ +class GeneratedBasesTest { + + @Test + void considersPropertiesOnGeneratedSourceAndTargetTypes() { + Order order = new Order(); + order.setItem( "my item" ); + order.setBaseName2( "my base name 2" ); + order.setBaseName1( "my base name 1" ); + + OrderDto dto = OrderMapper.INSTANCE.orderToDto( order ); + assertEquals( order.getItem(), dto.getItem() ); + assertEquals( order.getBaseName2(), dto.getBaseName2() ); + assertEquals( order.getBaseName1(), dto.getBaseName1() ); + + // Let's make sure several mappers can be generated + dto = AnotherOrderMapper.INSTANCE.orderToDto( order ); + assertEquals( order.getItem(), dto.getItem() ); + assertEquals( order.getBaseName2(), dto.getBaseName2() ); + assertEquals( order.getBaseName1(), dto.getBaseName1() ); + } +} 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 bb1ad5de9d..b444c1847a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -1,53 +1,58 @@ 4.0.0 - - org.sonatype.oss - oss-parent - 7 - - - org.mapstruct mapstruct-parent - 1.0.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.18.1 - 2.10.3 - 4.0.3.RELEASE - + + 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.27.7 + + + jdt_apt + + 1.8 + 3.25.5 + 2.3.2 + 2.3.0 @@ -63,7 +68,12 @@ gunnarmorling Gunnar Morling gunnar@mapstruct.org - http://www.gunnarmorling.de/ + https://www.morling.dev/ + + + filiphr + Filip Hrisafov + https://github.com/filiphr/ @@ -74,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 @@ -91,70 +114,102 @@ - - 3.0.3 - - org.freemarker freemarker - 2.3.21 + 2.3.34 - org.easytesting - fest-assert - 1.4 + org.assertj + assertj-core + ${assertj.version} com.google.guava guava - 14.0.1 + 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 checkstyle - 5.9 + ${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.0-SP4 + 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 - 1.1.13.Final + weld-core-impl + 3.1.8.Final + test org.glassfish @@ -185,27 +240,106 @@ 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.3 + 2.12.5 - + + - ${project.groupId} - mapstruct-common - ${project.version} + javax.xml.bind + jaxb-api + 2.3.1 - ${project.groupId} - mapstruct - ${project.version} + 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 + + + + + org.eclipse.tycho + tycho-compiler-jdt + ${org.eclipse.tycho.compiler-jdt.version} + + + org.codehaus.plexus + plexus-container-default + 1.7.1 + + + org.codehaus.plexus + plexus-component-annotations + 1.7.1 + + + org.codehaus.plexus + plexus-classworlds + 2.5.1 + + + org.codehaus.plexus + plexus-utils + 3.0.24 + + + commons-io + commons-io + 2.15.0 + + ${project.groupId} - mapstruct-jdk8 + mapstruct ${project.version} @@ -239,26 +373,26 @@ org.apache.maven.plugins maven-assembly-plugin - 2.4 + 3.8.0 org.apache.maven.plugins maven-checkstyle-plugin - 2.16 + 3.6.0 build-config/checkstyle.xml true true error true - basedir=${basedir} + basedir=${basedir} - **/*Prism.java,*/itest/jaxb/xsd/* + **/*Gem.java,*/itest/jaxb/xsd/* @@ -266,32 +400,35 @@ mapstruct-build-config ${project.version} + + com.puppycrawl.tools + checkstyle + ${com.puppycrawl.tools.checkstyle.version} + 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 @@ -301,20 +438,33 @@ de.andrena.tools.nopackagecycles no-package-cycles-enforcer-rule - 1.0.5 + 1.0.9 - + + org.apache.felix + maven-bundle-plugin + 5.1.1 + + + bundle-manifest + process-classes + + manifest + + + + 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 @@ -322,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 @@ -359,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 @@ -454,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 + @@ -517,9 +714,12 @@ org.codehaus.mojo.signature - java16 + java18 1.0 + + org.mapstruct.ap.internal.util.IgnoreJRERequirement + @@ -539,7 +739,7 @@ - [1.8,) + [${minimum.java.version},) @@ -557,7 +757,9 @@ test-compile - + + false + @@ -576,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 @@ -600,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 3ea91b66da..c753e3c1e4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,22 +1,9 @@ @@ -26,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.0.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
@@ -84,8 +70,21 @@ + documentation distribution + + test + + + release + !true + + + + integrationtest + +
diff --git a/processor/pom.xml b/processor/pom.xml index a22c6a1360..e317365aec 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -1,22 +1,9 @@ @@ -25,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.0.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,44 @@ 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 + test + - junit - junit + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine test - org.easytesting - fest-assert + org.assertj + assertj-core test @@ -78,12 +82,35 @@ com.puppycrawl.tools checkstyle test + + + com.sun + tools + + javax.inject javax.inject test + + jakarta.inject + jakarta.inject-api + test + + + jakarta.enterprise + jakarta.enterprise.cdi-api + test + + + + + org.codehaus.plexus + plexus-container-default + test + @@ -102,14 +129,39 @@ 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 + + @@ -120,16 +172,29 @@ true + + org.mapstruct.processor + annotation-processor +
+ + org.apache.maven.plugins + maven-javadoc-plugin + + all,-missing + + org.apache.maven.plugins maven-surefire-plugin - - compilation-tests_fork-${surefire.forkNumber} - + + + @{jacocoArgLine} -Xms1024m -Xmx3072m @@ -167,6 +232,12 @@ true + + + org.freemarker:* + org.mapstruct.tools.gem:gem-api + + org.freemarker:freemarker @@ -174,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 @@ -209,47 +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.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} - - @@ -283,6 +416,9 @@ + + jacocoArgLine + org.apache.maven.plugins @@ -297,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 12cddbf9a3..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-2015 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; @@ -23,32 +10,44 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +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.option.ReportingPolicy; -import org.mapstruct.ap.internal.prism.MapperPrism; import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext; import org.mapstruct.ap.internal.processor.ModelElementProcessor; import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext; import org.mapstruct.ap.internal.util.AnnotationProcessingException; +import org.mapstruct.ap.internal.util.AnnotationProcessorContext; +import org.mapstruct.ap.internal.util.RoundContext; +import org.mapstruct.ap.internal.util.Services; +import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; + +import static javax.lang.model.element.ElementKind.CLASS; /** * A JSR 269 annotation {@link Processor} which generates the implementations for mapper interfaces (interfaces @@ -70,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 @@ -83,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 { /** @@ -96,33 +89,84 @@ 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; - @Override - public synchronized void init(ProcessingEnvironment processingEnv) { - super.init( processingEnv ); + private AnnotationProcessorContext annotationProcessorContext; - options = createOptions(); + /** + * 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 + * processed in subsequent rounds. + *

    + * 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 anyway. + */ + private Set deferredMappers = new HashSet<>(); + + public MappingProcessor() { + Set additionalSupportedOptions; + String additionalSupportedOptionsError; + try { + additionalSupportedOptions = resolveAdditionalSupportedOptions(); + additionalSupportedOptionsError = null; + } + catch ( IllegalStateException ex ) { + additionalSupportedOptions = Collections.emptySet(); + additionalSupportedOptionsError = ex.getMessage(); + } + this.additionalSupportedOptions = additionalSupportedOptions; + this.additionalSupportedOptionsError = additionalSupportedOptionsError; } - private Options createOptions() { - String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); - String defaultComponentModel = processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ); + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init( processingEnv ); - return new Options( - Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), - Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), - unmappedTargetPolicy != null ? ReportingPolicy.valueOf( unmappedTargetPolicy ) : null, - defaultComponentModel == null ? "default" : defaultComponentModel, - 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 @@ -132,57 +176,164 @@ public SourceVersion getSupportedSourceVersion() { @Override public boolean process(final Set annotations, final RoundEnvironment roundEnvironment) { + // nothing to do in the last round + if ( !roundEnvironment.processingOver() ) { + RoundContext roundContext = new RoundContext( annotationProcessorContext ); - for ( TypeElement annotation : annotations ) { + // process any mappers left over from previous rounds + Set deferredMappers = getAndResetDeferredMappers(); + processMapperElements( deferredMappers, roundContext ); + + // get and process any mappers from this round + Set mappers = getMappers( annotations, roundEnvironment ); + 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() ); + + for ( DeferredMapper deferredMapper : deferredMappers ) { + TypeElement element = deferredMapper.deferredMapperElement; + deferred.add( processingEnv.getElementUtils().getTypeElement( element.getQualifiedName() ) ); + } + + deferredMappers.clear(); + return deferred; + } + + private Set getMappers(final Set annotations, + final RoundEnvironment roundEnvironment) { + Set mapperTypes = new HashSet<>(); + for ( TypeElement annotation : annotations ) { //Indicates that the annotation's type isn't on the class path of the compiled //project. Let the compiler deal with that and print an appropriate error. if ( annotation.getKind() != ElementKind.ANNOTATION_TYPE ) { continue; } - Set elementsWithAnnotation; try { - elementsWithAnnotation = roundEnvironment.getElementsAnnotatedWith( annotation ); - } - catch ( Throwable t ) { // whenever that may happen, but just to stay on the save side - handleUncaughtError( annotation, t ); - continue; - } - - for ( Element mapperElement : elementsWithAnnotation ) { - try { + Set annotatedMappers = roundEnvironment.getElementsAnnotatedWith( annotation ); + 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 ) { - continue; + if ( mapperTypeElement != null && MapperGem.instanceOn( mapperTypeElement ) != null ) { + mapperTypes.add( mapperTypeElement ); } - - // create a new context for each generated mapper in order to have imports of referenced types - // correctly managed; - // 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 ); - processMapperTypeElement( context, mapperTypeElement ); } - catch ( Throwable t ) { - handleUncaughtError( mapperElement, t ); - break; + } + catch ( Throwable t ) { // whenever that may happen, but just to stay on the save side + handleUncaughtError( annotation, t ); + continue; + } + } + return mapperTypes; + } + + 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 + // correctly managed; + // 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 + List tst = mapperElement.getEnclosedElements(); + ProcessorContext context = new DefaultModelElementProcessorContext( + processingEnv, + options, + roundContext, + getDeclaredTypesNotToBeImported( mapperElement ), + mapperElement + ); + + processMapperTypeElement( context, mapperElement ); + } + catch ( TypeHierarchyErroneousException thie ) { + 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 ); + break; } } + } - return ANNOTATIONS_CLAIMED_EXCLUSIVELY; + 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 ); @@ -225,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. @@ -239,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; @@ -267,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 5ffec43361..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-2015 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 70942463ea..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -26,6 +13,8 @@ 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; /** @@ -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 new file mode 100644 index 0000000000..99649a9a37 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/AbstractNumberToStringConversion.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.ap.internal.conversion; + +import java.text.DecimalFormat; +import java.text.ParseException; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Abstract base class for {@link PrimitiveToStringConversion}, {@link WrapperToStringConversion}, + * {@link BigDecimalToStringConversion} and {@link BigIntegerToStringConversion} + * + * Contains shared utility methods. + * + * @author Ciaran Liedeman + */ +public abstract class AbstractNumberToStringConversion extends SimpleConversion { + + private final boolean sourceTypeNumberSubclass; + + public AbstractNumberToStringConversion(boolean sourceTypeNumberSubclass) { + this.sourceTypeNumberSubclass = sourceTypeNumberSubclass; + } + + @Override + public Set getToConversionImportTypes(ConversionContext conversionContext) { + if ( requiresDecimalFormat( conversionContext ) ) { + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); + } + else { + return super.getToConversionImportTypes( conversionContext ); + } + } + + protected boolean requiresDecimalFormat(ConversionContext conversionContext) { + return sourceTypeNumberSubclass && conversionContext.getNumberFormat() != null; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + if ( requiresDecimalFormat( conversionContext ) ) { + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); + } + else { + return super.getFromConversionImportTypes( conversionContext ); + } + } + + @Override + protected List getFromConversionExceptionTypes(ConversionContext conversionContext) { + if ( requiresDecimalFormat( conversionContext ) ) { + return Collections.singletonList( conversionContext.getTypeFactory().getType( ParseException.class ) ); + } + else { + 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 d6f8ed6601..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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,7 @@ 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; /** @@ -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 67db380da8..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -24,6 +11,7 @@ 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; /** @@ -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 9828eb4b0c..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,29 +1,21 @@ -/** - * Copyright 2012-2015 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.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.HelperMethod; 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; /** @@ -31,20 +23,74 @@ * * @author Gunnar Morling */ -public class BigDecimalToStringConversion extends SimpleConversion { +public class BigDecimalToStringConversion extends AbstractNumberToStringConversion { + + public BigDecimalToStringConversion() { + super( true ); + } @Override public String getToExpression(ConversionContext conversionContext) { - return ".toString()"; + if ( requiresDecimalFormat( conversionContext ) ) { + StringBuilder sb = new StringBuilder(); + appendDecimalFormatter( sb, conversionContext ); + sb.append( ".format( )" ); + return sb.toString(); + } + else { + return ".toString()"; + } } @Override public String getFromExpression(ConversionContext conversionContext) { - return "new BigDecimal( )"; + if ( requiresDecimalFormat( conversionContext ) ) { + StringBuilder sb = new StringBuilder(); + sb.append( "(" ).append( bigDecimal( conversionContext ) ).append( ") " ); + appendDecimalFormatter( sb, conversionContext ); + sb.append( ".parse( )" ); + return sb.toString(); + } + else { + return "new " + bigDecimal( conversionContext ) + "( )"; + } } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { return asSet( conversionContext.getTypeFactory().getType( BigDecimal.class ) ); } + + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + 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) { + 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 6982ac65ac..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -25,6 +12,7 @@ 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; /** @@ -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 69e80e8f30..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -24,6 +11,7 @@ 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; /** @@ -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 156354603c..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,29 +1,23 @@ -/** - * Copyright 2012-2015 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.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.HelperMethod; 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; /** @@ -31,20 +25,80 @@ * * @author Gunnar Morling */ -public class BigIntegerToStringConversion extends SimpleConversion { +public class BigIntegerToStringConversion extends AbstractNumberToStringConversion { + + public BigIntegerToStringConversion() { + super( true ); + } @Override public String getToExpression(ConversionContext conversionContext) { - return ".toString()"; + if ( requiresDecimalFormat( conversionContext ) ) { + StringBuilder sb = new StringBuilder(); + appendDecimalFormatter( sb, conversionContext ); + sb.append( ".format( )" ); + return sb.toString(); + } + else { + return ".toString()"; + } } @Override public String getFromExpression(ConversionContext conversionContext) { - return "new BigInteger( )"; + if ( requiresDecimalFormat( conversionContext ) ) { + StringBuilder sb = new StringBuilder(); + sb.append( "( (" ).append( bigDecimal( conversionContext ) ).append( ") " ); + appendDecimalFormatter( sb, conversionContext ); + sb.append( ".parse( )" ); + sb.append( " ).toBigInteger()" ); + return sb.toString(); + } + else { + return "new " + bigInteger( conversionContext ) + "( )"; + } } @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { - return asSet( conversionContext.getTypeFactory().getType( BigInteger.class ) ); + if ( requiresDecimalFormat( conversionContext ) ) { + return asSet( conversionContext.getTypeFactory().getType( BigDecimal.class ) ); + } + else { + return asSet( conversionContext.getTypeFactory().getType( BigInteger.class ) ); + } + } + + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + 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) { + 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 9ba2e39b94..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -25,6 +12,7 @@ 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; /** @@ -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 8ec6ca11c1..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-2015 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 d5b6258636..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-2015 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 e1a79ea0ec..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,26 +1,18 @@ -/** - * Copyright 2012-2015 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.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.common.FieldReference; /** * Implementations create inline {@link TypeConversion}s such as @@ -52,4 +44,25 @@ public interface ConversionProvider { * @return A conversion from target to source. */ 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 662c4bab3d..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,38 +1,38 @@ -/** - * Copyright 2012-2015 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.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.reverse; +import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; /** * Holds built-in {@link ConversionProvider}s such as from {@code int} to {@code String}. @@ -41,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,6 +182,7 @@ public Conversions(Elements elementUtils, TypeFactory typeFactory) { register( Character.class, String.class, new CharWrapperToStringConversion() ); register( BigInteger.class, String.class, new BigIntegerToStringConversion() ); register( BigDecimal.class, String.class, new BigDecimalToStringConversion() ); + register( StringBuilder.class, String.class, new StringBuilderToStringConversion() ); registerJodaConversions(); @@ -187,8 +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() ); + + 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() { @@ -212,39 +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() ); + 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 ) ); @@ -278,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) { @@ -291,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; } @@ -341,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 new file mode 100644 index 0000000000..d1b49cff69 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java @@ -0,0 +1,92 @@ +/* + * 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.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} + * + * {@code DecimalFormat df = new DecimalFormat( numberFormat )} + * with setParseBigDecimal set to true. + * + * @author Sjaak Derksen + */ +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, 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 ); + 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 + public Set getImportTypes() { + return importTypes; + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + 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 new file mode 100644 index 0000000000..bc2aae7305 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlDateConversion.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.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}. + * + * @author Filip Hrisafov + */ +public class DateToSqlDateConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return "new " + sqlDate( conversionContext ) + "( .getTime() )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.singleton( conversionContext.getTypeFactory().getType( Date.class ) ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ""; + } +} 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 new file mode 100644 index 0000000000..ef892273e3 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimeConversion.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.sql.Time; +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.time; + +/** + * Conversion between {@link java.util.Date} and {@link java.sql.Time}. + * + * @author Filip Hrisafov + */ +public class DateToSqlTimeConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return "new " + time( conversionContext ) + "( .getTime() )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTypeFactory().getType( Time.class ) ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ""; + } +} 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 new file mode 100644 index 0000000000..cba40e7c5e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/DateToSqlTimestampConversion.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.sql.Timestamp; +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.timestamp; + +/** + * Conversion between {@link java.util.Date} and {@link java.sql.Timestamp}. + * + * @author Filip Hrisafov + */ +public class DateToSqlTimestampConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return "new " + timestamp( conversionContext ) + "( .getTime() )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTypeFactory().getType( Timestamp.class ) ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return ""; + } +} 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 8cba76baa6..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -22,13 +9,18 @@ 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.AssignmentFactory; -import org.mapstruct.ap.internal.model.assignment.Assignment; +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 java.util.Arrays.asList; +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; /** @@ -40,29 +32,54 @@ public class DateToStringConversion implements ConversionProvider { @Override public Assignment to(ConversionContext conversionContext) { - return AssignmentFactory.createTypeConversion( - 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 AssignmentFactory.createTypeConversion( - 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" ) ); } + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + 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( ")." ); @@ -71,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 215e7c466e..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -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 40f0da5d58..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-2015 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.systemDefault() )"; + 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 36965d3ef3..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-2015 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 new file mode 100644 index 0000000000..b28bc50785 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/JavaLocalDateToDateConversion.java @@ -0,0 +1,58 @@ +/* + * 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.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 + * {@link java.util.Date} and vice versa. + */ +public class JavaLocalDateToDateConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + 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 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 65735f4002..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-2015 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 278b894740..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-2015 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 23290e40ae..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-2015 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 56ffe22722..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-2015 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 ff37151ba0..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -25,6 +12,7 @@ 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; /** @@ -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 c0e3aa88a7..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-2015 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 3eef1a2467..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-2015 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 d5b1b21d05..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-2015 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 1f1fcb0fa7..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-2015 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 8164ce71aa..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,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -45,7 +32,7 @@ protected String getToExpression(ConversionContext conversionContext) { @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/LocaleToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.java new file mode 100644 index 0000000000..05a6da0b19 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/LocaleToStringConversion.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.Locale; +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.locale; + +/** + * Conversion between {@link java.util.Locale} and {@link String}. + * + * @author Jason Bodnar + */ +public class LocaleToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toLanguageTag()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return locale( conversionContext ) + ".forLanguageTag( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTypeFactory().getType( Locale.class ) ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.java new file mode 100644 index 0000000000..db9480662d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/OptionalWrapperConversionProvider.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.ap.internal.conversion; + +import java.util.List; + +import org.mapstruct.ap.internal.model.FromOptionalTypeConversion; +import org.mapstruct.ap.internal.model.HelperMethod; +import org.mapstruct.ap.internal.model.ToOptionalTypeConversion; +import org.mapstruct.ap.internal.model.common.Assignment; +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.model.common.TypeFactory; + +/** + * A conversion provider that wraps / unwraps the underlying conversion in Optional. + * e.g., For conversion from {@code Optional} to {@code Integer}. + * + * @author Filip Hrisafov + */ +public class OptionalWrapperConversionProvider implements ConversionProvider { + + private final ConversionProvider conversionProvider; + + public OptionalWrapperConversionProvider(ConversionProvider conversionProvider) { + this.conversionProvider = conversionProvider; + } + + @Override + public Assignment to(ConversionContext conversionContext) { + Assignment assignment = conversionProvider.to( new OptionalConversionContext( conversionContext ) ); + return new ToOptionalTypeConversion( conversionContext.getTargetType(), assignment ); + } + + @Override + public Assignment from(ConversionContext conversionContext) { + Assignment assignment = conversionProvider.to( new OptionalConversionContext( conversionContext ) ); + return new FromOptionalTypeConversion( conversionContext.getSourceType(), assignment ); + } + + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperMethods( conversionContext ); + } + + @Override + public List getRequiredHelperFields(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperFields( conversionContext ); + } + + private static class OptionalConversionContext implements ConversionContext { + + private final ConversionContext delegate; + + private OptionalConversionContext(ConversionContext delegate) { + this.delegate = delegate; + } + + @Override + public Type getTargetType() { + return resolveType( delegate.getTargetType() ); + } + + @Override + public Type getSourceType() { + return resolveType( delegate.getSourceType() ); + } + + private Type resolveType(Type type) { + if ( type.isOptionalType() ) { + return type.getOptionalBaseType(); + } + return type; + } + + @Override + public String getDateFormat() { + return delegate.getDateFormat(); + } + + @Override + public String getNumberFormat() { + return delegate.getNumberFormat(); + } + + @Override + public String getLocale() { + return delegate.getLocale(); + } + + @Override + public TypeFactory getTypeFactory() { + return delegate.getTypeFactory(); + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToPrimitiveConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToPrimitiveConversion.java index 4549bf8463..34f787b191 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToPrimitiveConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToPrimitiveConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -30,10 +17,6 @@ public class PrimitiveToPrimitiveConversion extends SimpleConversion { private final Class sourceType; public PrimitiveToPrimitiveConversion(Class sourceType) { - if ( !sourceType.isPrimitive() ) { - throw new IllegalArgumentException( sourceType + " is no primitive type." ); - } - this.sourceType = sourceType; } @@ -46,4 +29,5 @@ public String getToExpression(ConversionContext conversionContext) { public String getFromExpression(ConversionContext conversionContext) { return "(" + sourceType + ") "; } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java index 954fc32304..909ce8c0f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java @@ -1,39 +1,39 @@ -/** - * Copyright 2012-2015 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.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Collections; +import java.util.Locale; +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.NativeTypes; import org.mapstruct.ap.internal.util.Strings; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormat; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormatSymbols; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between primitive types such as {@code byte} or {@code long} and * {@link String}. * * @author Gunnar Morling */ -public class PrimitiveToStringConversion extends SimpleConversion { +public class PrimitiveToStringConversion extends AbstractNumberToStringConversion { private final Class sourceType; private final Class wrapperType; public PrimitiveToStringConversion(Class sourceType) { + super( NativeTypes.isNumber( sourceType ) ); if ( !sourceType.isPrimitive() ) { throw new IllegalArgumentException( sourceType + " is no primitive type." ); } @@ -44,12 +44,88 @@ public PrimitiveToStringConversion(Class sourceType) { @Override public String getToExpression(ConversionContext conversionContext) { - return "String.valueOf( )"; + if ( requiresDecimalFormat( conversionContext ) ) { + StringBuilder sb = new StringBuilder(); + appendDecimalFormatter( sb, conversionContext ); + sb.append( ".format( )" ); + return sb.toString(); + } + else { + return "String.valueOf( )"; + } + } + + @Override + public Set getToConversionImportTypes(ConversionContext conversionContext) { + if ( requiresDecimalFormat( conversionContext ) ) { + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); + } + + return Collections.emptySet(); } @Override public String getFromExpression(ConversionContext conversionContext) { - return wrapperType.getSimpleName() + ".parse" + - Strings.capitalize( sourceType.getSimpleName() ) + "( )"; + if ( requiresDecimalFormat( conversionContext ) ) { + StringBuilder sb = new StringBuilder(); + appendDecimalFormatter( sb, conversionContext ); + sb.append( ".parse( )." ); + sb.append( sourceType.getSimpleName() ); + sb.append( "Value()" ); + return sb.toString(); + } + else { + return wrapperType.getSimpleName() + ".parse" + + Strings.capitalize( sourceType.getSimpleName() ) + "( )"; + } + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + if ( requiresDecimalFormat( conversionContext ) ) { + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); + } + + return Collections.emptySet(); + } + + private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { + sb.append( "new " ); + sb.append( decimalFormat( conversionContext ) ); + sb.append( "( " ); + + if ( conversionContext.getNumberFormat() != null ) { + sb.append( "\"" ); + sb.append( conversionContext.getNumberFormat() ); + sb.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + sb.append( ", " ) + .append( decimalFormatSymbols( conversionContext ) ) + .append( ".getInstance( " ) + .append( locale( conversionContext ) ) + .append( ".forLanguageTag( \"" ) + .append( conversionContext.getLocale() ) + .append( " \" ) )" ); + } + } + + sb.append( " )" ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToWrapperConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToWrapperConversion.java index 66f1b495b9..dc1bde1d05 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToWrapperConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToWrapperConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -33,13 +20,6 @@ public class PrimitiveToWrapperConversion extends SimpleConversion { private final Class targetType; public PrimitiveToWrapperConversion(Class sourceType, Class targetType) { - if ( !sourceType.isPrimitive() ) { - throw new IllegalArgumentException( sourceType + " is no primitive type." ); - } - if ( targetType.isPrimitive() ) { - throw new IllegalArgumentException( targetType + " is no wrapper type." ); - } - this.sourceType = sourceType; this.targetType = NativeTypes.getPrimitiveType( targetType ); } @@ -58,4 +38,5 @@ public String getToExpression(ConversionContext conversionContext) { public String getFromExpression(ConversionContext conversionContext) { return "." + sourceType.getName() + "Value()"; } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ReverseConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ReverseConversion.java index 1b36b60838..cb496b0c54 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ReverseConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ReverseConversion.java @@ -1,28 +1,19 @@ -/** - * Copyright 2012-2015 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 org.mapstruct.ap.internal.model.assignment.Assignment; +import java.util.List; + +import org.mapstruct.ap.internal.model.HelperMethod; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.FieldReference; /** - * A {@link ConversionProvider} which creates the reversed conversions for a + * * A {@link ConversionProvider} which creates the inversed conversions for a * given conversion provider. * * @author Gunnar Morling @@ -31,7 +22,7 @@ public class ReverseConversion implements ConversionProvider { private ConversionProvider conversionProvider; - public static ReverseConversion reverse(ConversionProvider conversionProvider) { + public static ReverseConversion inverse(ConversionProvider conversionProvider) { return new ReverseConversion( conversionProvider ); } @@ -48,4 +39,14 @@ public Assignment to(ConversionContext conversionContext) { public Assignment from(ConversionContext conversionContext) { return conversionProvider.to( conversionContext ); } + + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperMethods( conversionContext ); + } + + @Override + public List getRequiredHelperFields(ConversionContext conversionContext) { + return conversionProvider.getRequiredHelperFields( conversionContext ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/SimpleConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/SimpleConversion.java index 6826b7f386..91051a3e23 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/SimpleConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/SimpleConversion.java @@ -1,29 +1,17 @@ -/** - * Copyright 2012-2015 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.util.Collections; +import java.util.List; import java.util.Set; -import org.mapstruct.ap.internal.model.AssignmentFactory; +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.common.Type; @@ -37,9 +25,8 @@ public abstract class SimpleConversion implements ConversionProvider { @Override public Assignment to(ConversionContext conversionContext) { String toExpression = getToExpression( conversionContext ); - return AssignmentFactory.createTypeConversion( - getToConversionImportTypes( conversionContext ), - Collections.emptyList(), + return new TypeConversion( getToConversionImportTypes( conversionContext ), + getToConversionExceptionTypes( conversionContext ), toExpression ); } @@ -47,13 +34,17 @@ public Assignment to(ConversionContext conversionContext) { @Override public Assignment from(ConversionContext conversionContext) { String fromExpression = getFromExpression( conversionContext ); - return AssignmentFactory.createTypeConversion( - getFromConversionImportTypes( conversionContext ), - Collections.emptyList(), + return new TypeConversion( getFromConversionImportTypes( conversionContext ), + getFromConversionExceptionTypes( conversionContext ), fromExpression ); } + @Override + public List getRequiredHelperMethods(ConversionContext conversionContext) { + return Collections.emptyList(); + } + /** * Returns the conversion string from source to target. The placeholder {@code } can be used to represent a * reference to the source value. @@ -83,7 +74,7 @@ public Assignment from(ConversionContext conversionContext) { * @return conversion types required in the "from" conversion */ protected Set getFromConversionImportTypes(ConversionContext conversionContext) { - return Collections.emptySet(); + return Collections.emptySet(); } /** @@ -95,6 +86,15 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon * @return conversion types required in the "to" conversion */ protected Set getToConversionImportTypes(ConversionContext conversionContext) { - return Collections.emptySet(); + return Collections.emptySet(); } + + protected List getToConversionExceptionTypes(ConversionContext conversionContext) { + return Collections.emptyList(); + } + + protected List getFromConversionExceptionTypes(ConversionContext conversionContext) { + return Collections.emptyList(); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/StaticParseToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/StaticParseToStringConversion.java new file mode 100644 index 0000000000..df3a70ac34 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/StaticParseToStringConversion.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.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; + +/** + * Handles conversion between a target type {@code T} and {@link String}, + * where {@code T#parse(String)} and {@code T#toString} are inverse operations. + * The {@link ConversionContext#getTargetType()} is used as the from target type {@code T}. + */ +public class StaticParseToStringConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().createReferenceName() + ".parse( )"; + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTargetType() ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/StringBuilderToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/StringBuilderToStringConversion.java new file mode 100644 index 0000000000..2f6705fab8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/StringBuilderToStringConversion.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.ap.internal.conversion; + +import org.mapstruct.ap.internal.model.common.ConversionContext; + +/** + * Handles conversion between a target type {@link StringBuilder} and {@link String}. + * + */ +public class StringBuilderToStringConversion extends SimpleConversion { + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return "new " + ConversionUtils.stringBuilder( conversionContext ) + "( )"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java new file mode 100644 index 0000000000..dbab6bc4ca --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/TypeToOptionalConversion.java @@ -0,0 +1,46 @@ +/* + * 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.Collections; +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.Strings; + +import static org.mapstruct.ap.internal.conversion.ReverseConversion.inverse; + +/** + * Conversion between {@link java.util.Optional Optional} and its base type. + * + * @author Filip Hrisafov + */ +public class TypeToOptionalConversion extends SimpleConversion { + + static final TypeToOptionalConversion TYPE_TO_OPTIONAL_CONVERSION = new TypeToOptionalConversion(); + static final ConversionProvider OPTIONAL_TO_TYPE_CONVERSION = inverse( TYPE_TO_OPTIONAL_CONVERSION ); + + @Override + protected String getToExpression(ConversionContext conversionContext) { + return conversionContext.getTargetType().asRawType().createReferenceName() + ".of( )"; + } + + @Override + protected Set getToConversionImportTypes(ConversionContext conversionContext) { + return Collections.singleton( conversionContext.getTargetType().asRawType() ); + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + StringBuilder sb = new StringBuilder(".get"); + Type optionalBaseType = conversionContext.getSourceType().getOptionalBaseType(); + if ( optionalBaseType.isPrimitive() ) { + sb.append( "As" ).append( Strings.capitalize( optionalBaseType.getName() ) ); + } + return sb.append( "()" ).toString(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.java new file mode 100644 index 0000000000..c25383ae05 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URIToStringConversion.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.net.URI; +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.uri; +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Conversion between {@link java.net.URI} and {@link String}. + * + * @author Maciej Kucharczyk + */ +public class URIToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return uri( conversionContext ) + ".create( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return asSet( conversionContext.getTypeFactory().getType( URI.class ) ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/URLToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URLToStringConversion.java new file mode 100644 index 0000000000..b1fd5f599a --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URLToStringConversion.java @@ -0,0 +1,46 @@ +/* + * 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.net.MalformedURLException; +import java.net.URI; +import java.util.List; +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.uri; +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Conversion between {@link java.net.URL} and {@link String}. + * + * @author Adam Szatyin + */ +public class URLToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return uri( conversionContext ) + ".create( ).toURL()"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return asSet( conversionContext.getTypeFactory().getType( URI.class ) ); + } + + @Override + protected List getFromConversionExceptionTypes(ConversionContext conversionContext) { + return java.util.Collections.singletonList( + conversionContext.getTypeFactory().getType( MalformedURLException.class ) + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/UUIDToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/UUIDToStringConversion.java new file mode 100644 index 0000000000..b07d799387 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/UUIDToStringConversion.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.Set; +import java.util.UUID; + +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.uuid; + +/** + * Conversion between {@link java.util.UUID} and {@link String}. + * + * @author Jason Bodnar + */ +public class UUIDToStringConversion extends SimpleConversion { + @Override + protected String getToExpression(ConversionContext conversionContext) { + return ".toString()"; + } + + @Override + protected String getFromExpression(ConversionContext conversionContext) { + return uuid( conversionContext ) + ".fromString( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return Collections.asSet( conversionContext.getTypeFactory().getType( UUID.class ) ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java index f6e1a34c08..dd7b6bac8d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToStringConversion.java @@ -1,38 +1,38 @@ -/** - * Copyright 2012-2015 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.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Collections; +import java.util.Locale; +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.NativeTypes; import org.mapstruct.ap.internal.util.Strings; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormat; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.decimalFormatSymbols; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; + /** * Conversion between wrapper types such as {@link Integer} and {@link String}. * * @author Gunnar Morling */ -public class WrapperToStringConversion extends SimpleConversion { +public class WrapperToStringConversion extends AbstractNumberToStringConversion { private final Class sourceType; private final Class primitiveType; public WrapperToStringConversion(Class sourceType) { + super( NativeTypes.isNumber( sourceType ) ); if ( sourceType.isPrimitive() ) { throw new IllegalArgumentException( sourceType + " is no wrapper type." ); } @@ -43,12 +43,88 @@ public WrapperToStringConversion(Class sourceType) { @Override public String getToExpression(ConversionContext conversionContext) { - return "String.valueOf( )"; + if ( requiresDecimalFormat( conversionContext ) ) { + StringBuilder sb = new StringBuilder(); + appendDecimalFormatter( sb, conversionContext ); + sb.append( ".format( )" ); + return sb.toString(); + } + else { + return "String.valueOf( )"; + } + } + + @Override + public Set getToConversionImportTypes(ConversionContext conversionContext) { + if ( requiresDecimalFormat( conversionContext ) ) { + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); + } + + return Collections.emptySet(); } @Override public String getFromExpression(ConversionContext conversionContext) { - return sourceType.getSimpleName() + ".parse" + - Strings.capitalize( primitiveType.getSimpleName() ) + "( )"; + if ( requiresDecimalFormat( conversionContext ) ) { + StringBuilder sb = new StringBuilder(); + appendDecimalFormatter( sb, conversionContext ); + sb.append( ".parse( )." ); + sb.append( primitiveType.getSimpleName() ); + sb.append( "Value()" ); + return sb.toString(); + } + else { + return sourceType.getSimpleName() + ".parse" + + Strings.capitalize( primitiveType.getSimpleName() ) + "( )"; + } + } + + @Override + protected Set getFromConversionImportTypes(ConversionContext conversionContext) { + if ( requiresDecimalFormat( conversionContext ) ) { + if ( conversionContext.getLocale() != null ) { + return asSet( + conversionContext.getTypeFactory().getType( DecimalFormat.class ), + conversionContext.getTypeFactory().getType( DecimalFormatSymbols.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + + return Collections.singleton( conversionContext.getTypeFactory().getType( DecimalFormat.class ) ); + } + + return Collections.emptySet(); + } + + private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { + sb.append( "new " ); + sb.append( decimalFormat( conversionContext ) ); + sb.append( "( " ); + + if ( conversionContext.getNumberFormat() != null ) { + sb.append( "\"" ); + sb.append( conversionContext.getNumberFormat() ); + sb.append( "\"" ); + + if ( conversionContext.getLocale() != null ) { + sb.append( ", " ) + .append( decimalFormatSymbols( conversionContext ) ) + .append( ".getInstance( " ) + .append( locale( conversionContext ) ) + .append( ".forLanguageTag( \"" ) + .append( conversionContext.getLocale() ) + .append( " \" ) )" ); + } + } + + sb.append( " )" ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToWrapperConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToWrapperConversion.java index e15488d5a5..a16ac87274 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToWrapperConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/WrapperToWrapperConversion.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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; @@ -32,13 +19,6 @@ public class WrapperToWrapperConversion extends SimpleConversion { private final Class targetType; public WrapperToWrapperConversion(Class sourceType, Class targetType) { - if ( sourceType.isPrimitive() ) { - throw new IllegalArgumentException( sourceType + " is no wrapper type." ); - } - if ( targetType.isPrimitive() ) { - throw new IllegalArgumentException( targetType + " is no wrapper type." ); - } - this.sourceType = NativeTypes.getPrimitiveType( sourceType ); this.targetType = NativeTypes.getPrimitiveType( targetType ); } @@ -62,4 +42,5 @@ public String getFromExpression(ConversionContext conversionContext) { return "." + sourceType.getName() + "Value()"; } } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/package-info.java index bd164ad3a5..b1002b6a8d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

    diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.java new file mode 100644 index 0000000000..609c6d04e7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ClassAccessibilityGem.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.ap.internal.gem; + +/** + * Gem for the enum {@link org.mapstruct.ClassAccessibility} + * + * @author Raimund Klein + */ +public enum ClassAccessibilityGem { + DEFAULT, PUBLIC, PACKAGE_PRIVATE +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/CollectionMappingStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/CollectionMappingStrategyGem.java new file mode 100644 index 0000000000..f88e21a763 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/CollectionMappingStrategyGem.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.ap.internal.gem; + +/** + * Gem for the enum {@link org.mapstruct.CollectionMappingStrategy} + * + * @author Andreas Gudian + */ +public enum CollectionMappingStrategyGem { + + ACCESSOR_ONLY, + SETTER_PREFERRED, + ADDER_PREFERRED, + TARGET_IMMUTABLE; +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java new file mode 100644 index 0000000000..adea4b4c1f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.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.ap.internal.gem; + +/** + * @author Filip Hrisafov + */ +public enum ConditionStrategyGem { + + PROPERTIES, + SOURCE_PARAMETERS +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java new file mode 100644 index 0000000000..3036faf2eb --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -0,0 +1,94 @@ +/* + * 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.gem; + +import javax.xml.bind.annotation.XmlElementDecl; +import javax.xml.bind.annotation.XmlElementRef; + +import org.mapstruct.AfterMapping; +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWiths; +import org.mapstruct.BeanMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Builder; +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.DecoratedWith; +import org.mapstruct.EnumMapping; +import org.mapstruct.Ignored; +import org.mapstruct.IgnoredList; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.IterableMapping; +import org.mapstruct.Javadoc; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.ObjectFactory; +import org.mapstruct.Qualifier; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.SubclassMapping; +import org.mapstruct.SubclassMappings; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.control.MappingControl; +import org.mapstruct.control.MappingControls; +import org.mapstruct.tools.gem.GemDefinition; + +/** + * Triggers the generation of gem types using Gem Tools. + * + * @author Gunnar Morling + */ +@GemDefinition(Deprecated.class) +@GemDefinition(AnnotateWith.class) +@GemDefinition(AnnotateWith.Element.class) +@GemDefinition(AnnotateWiths.class) +@GemDefinition(Mapper.class) +@GemDefinition(Mapping.class) +@GemDefinition(Ignored.class) +@GemDefinition(IgnoredList.class) +@GemDefinition(Mappings.class) +@GemDefinition(IterableMapping.class) +@GemDefinition(BeanMapping.class) +@GemDefinition(EnumMapping.class) +@GemDefinition(MapMapping.class) +@GemDefinition(SourcePropertyName.class) +@GemDefinition(SubclassMapping.class) +@GemDefinition(SubclassMappings.class) +@GemDefinition(TargetType.class) +@GemDefinition(TargetPropertyName.class) +@GemDefinition(MappingTarget.class) +@GemDefinition(DecoratedWith.class) +@GemDefinition(MapperConfig.class) +@GemDefinition(InheritConfiguration.class) +@GemDefinition(InheritInverseConfiguration.class) +@GemDefinition(Qualifier.class) +@GemDefinition(Named.class) +@GemDefinition(ObjectFactory.class) +@GemDefinition(AfterMapping.class) +@GemDefinition(BeforeMapping.class) +@GemDefinition(ValueMapping.class) +@GemDefinition(ValueMappings.class) +@GemDefinition(Context.class) +@GemDefinition(Builder.class) +@GemDefinition(Condition.class) +@GemDefinition(Javadoc.class) + +@GemDefinition(MappingControl.class) +@GemDefinition(MappingControls.class) + +// external types +@GemDefinition(XmlElementDecl.class) +@GemDefinition(XmlElementRef.class) +public class GemGenerator { +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/InjectionStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/InjectionStrategyGem.java new file mode 100644 index 0000000000..621d07fd09 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/InjectionStrategyGem.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.ap.internal.gem; + +/** + * Gem for the enum {@link org.mapstruct.InjectionStrategy}. + * + * @author Kevin Grüneberg + */ +public enum InjectionStrategyGem { + + FIELD, + CONSTRUCTOR, + SETTER; +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java new file mode 100644 index 0000000000..bc58024ca3 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingConstantsGem.java @@ -0,0 +1,58 @@ +/* + * 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.gem; + +/** + * Gem for the enum {@link org.mapstruct.MappingConstants} + * + * @author Sjaak Derksen + */ +public final class MappingConstantsGem { + + private MappingConstantsGem() { + } + + public static final String NULL = ""; + + public static final String ANY_REMAINING = ""; + + public static final String ANY_UNMAPPED = ""; + + public static final String THROW_EXCEPTION = ""; + + public static final String SUFFIX_TRANSFORMATION = "suffix"; + + public static final String STRIP_SUFFIX_TRANSFORMATION = "stripSuffix"; + + public static final String PREFIX_TRANSFORMATION = "prefix"; + + public static final String STRIP_PREFIX_TRANSFORMATION = "stripPrefix"; + + public static final String CASE_TRANSFORMATION = "case"; + + /** + * Gem for the class {@link org.mapstruct.MappingConstants.ComponentModel} + * + */ + public final class ComponentModelGem { + + private ComponentModelGem() { + } + + public static final String DEFAULT = "default"; + + public static final String CDI = "cdi"; + + public static final String SPRING = "spring"; + + public static final String JSR330 = "jsr330"; + + public static final String JAKARTA = "jakarta"; + + public static final String JAKARTA_CDI = "jakarta-cdi"; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingControlUseGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingControlUseGem.java new file mode 100644 index 0000000000..94c8350ef8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingControlUseGem.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.ap.internal.gem; + +/** + * + * @author Sjaak + */ +public enum MappingControlUseGem { + + BUILT_IN_CONVERSION, + COMPLEX_MAPPING, + DIRECT, + MAPPING_METHOD +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingInheritanceStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingInheritanceStrategyGem.java new file mode 100644 index 0000000000..b07030d8fc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/MappingInheritanceStrategyGem.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.ap.internal.gem; + + +/** + * Gem for the enum {@link org.mapstruct.MappingInheritanceStrategy} + * + * @author Andreas Gudian + */ +public enum MappingInheritanceStrategyGem { + + EXPLICIT( false, false, false ), + AUTO_INHERIT_FROM_CONFIG( true, true, false ), + AUTO_INHERIT_REVERSE_FROM_CONFIG( true, false, true ), + AUTO_INHERIT_ALL_FROM_CONFIG( true, true, true ); + + private final boolean autoInherit; + private final boolean applyForward; + private final boolean applyReverse; + + MappingInheritanceStrategyGem(boolean isAutoInherit, boolean applyForward, boolean applyReverse) { + this.autoInherit = isAutoInherit; + this.applyForward = applyForward; + this.applyReverse = applyReverse; + } + + public boolean isAutoInherit() { + return autoInherit; + } + + public boolean isApplyForward() { + return applyForward; + } + + public boolean isApplyReverse() { + return applyReverse; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValueCheckStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValueCheckStrategyGem.java new file mode 100644 index 0000000000..b7ab803232 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValueCheckStrategyGem.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.ap.internal.gem; + + +/** + * Gem for the enum {@link org.mapstruct.NullValueCheckStrategy} + * + * @author Sean Huang + */ +public enum NullValueCheckStrategyGem { + + ON_IMPLICIT_CONVERSION, + ALWAYS; +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValueMappingStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValueMappingStrategyGem.java new file mode 100644 index 0000000000..3d8991f820 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValueMappingStrategyGem.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.ap.internal.gem; + +/** + * Gem for the enum {@link org.mapstruct.NullValueMappingStrategy} + * + * @author Sjaak Derksen + */ +public enum NullValueMappingStrategyGem { + + RETURN_NULL( false ), + RETURN_DEFAULT( true ); + + private final boolean returnDefault; + + NullValueMappingStrategyGem(boolean returnDefault) { + this.returnDefault = returnDefault; + } + + public boolean isReturnDefault() { + return returnDefault; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.java new file mode 100644 index 0000000000..93c98b73ac --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/NullValuePropertyMappingStrategyGem.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.ap.internal.gem; + + +/** + * Gem for the enum {@link org.mapstruct.NullValuePropertyMappingStrategy} + * + * @author Sjaak Derksen + */ +public enum NullValuePropertyMappingStrategyGem { + + SET_TO_NULL, + SET_TO_DEFAULT, + IGNORE, + CLEAR; +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ReportingPolicyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ReportingPolicyGem.java new file mode 100644 index 0000000000..d985c79709 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ReportingPolicyGem.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.ap.internal.gem; + +import javax.tools.Diagnostic; +import javax.tools.Diagnostic.Kind; + +/** + * Gem for the enum {@link org.mapstruct.ReportingPolicy}. + * + * @author Gunnar Morling + */ +public enum ReportingPolicyGem { + + IGNORE( null, false, false ), + WARN( Kind.WARNING, true, false ), + ERROR( Kind.ERROR, true, true ); + + private final Diagnostic.Kind diagnosticKind; + private final boolean requiresReport; + private final boolean failsBuild; + + ReportingPolicyGem(Diagnostic.Kind diagnosticKind, boolean requiresReport, boolean failsBuild) { + this.requiresReport = requiresReport; + this.diagnosticKind = diagnosticKind; + this.failsBuild = failsBuild; + } + + public Diagnostic.Kind getDiagnosticKind() { + return diagnosticKind; + } + + public boolean requiresReport() { + return requiresReport; + } + + public boolean failsBuild() { + return failsBuild; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/SubclassExhaustiveStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/SubclassExhaustiveStrategyGem.java new file mode 100644 index 0000000000..2367d649a9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/SubclassExhaustiveStrategyGem.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.ap.internal.gem; + +/** + * Gem for the enum {@link org.mapstruct.SubclassExhaustiveStrategy} + * + * @author Ben Zegveld + */ +public enum SubclassExhaustiveStrategyGem { + + COMPILE_ERROR( false ), + RUNTIME_EXCEPTION( true ); + + private final boolean abstractReturnTypeAllowed; + + SubclassExhaustiveStrategyGem(boolean abstractReturnTypeAllowed) { + this.abstractReturnTypeAllowed = abstractReturnTypeAllowed; + } + + public boolean isAbstractReturnTypeAllowed() { + return abstractReturnTypeAllowed; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/jakarta/JakartaGemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/jakarta/JakartaGemGenerator.java new file mode 100644 index 0000000000..93bdebeaef --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/jakarta/JakartaGemGenerator.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.ap.internal.gem.jakarta; + +import jakarta.xml.bind.annotation.XmlElementDecl; +import jakarta.xml.bind.annotation.XmlElementRef; +import org.mapstruct.tools.gem.GemDefinition; + +/** + * This class is a temporary solution to an issue in the Gem Tools library. + * + *

    + * This class can be merged with {@link org.mapstruct.ap.internal.gem.GemGenerator} + * after the mentioned issue is resolved. + *

    + * + * @see Gem Tools issue #10 + * @author Iaroslav Bogdanchikov + */ +@GemDefinition(XmlElementDecl.class) +@GemDefinition(XmlElementRef.class) +class JakartaGemGenerator { +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/package-info.java new file mode 100644 index 0000000000..b5f4330e4e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/package-info.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 + */ +/** + *

    + * This package contains the generated gem types for accessing the MapStruct annotations in a comfortable way. + *

    + */ +package org.mapstruct.ap.internal.gem; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java new file mode 100644 index 0000000000..8859e542b8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -0,0 +1,188 @@ +/* + * 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.model; + +import java.util.function.Supplier; +import javax.lang.model.element.AnnotationMirror; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.common.SourceRHS; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.MappingMethodUtils; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.util.Message; + +/** + * @author Filip Hrisafov + */ +class AbstractBaseBuilder> { + + protected B myself; + protected MappingBuilderContext ctx; + protected Method method; + + AbstractBaseBuilder(Class selfType) { + myself = selfType.cast( this ); + } + + public B mappingContext(MappingBuilderContext mappingContext) { + this.ctx = mappingContext; + return myself; + } + + public B method(Method sourceMethod) { + this.method = sourceMethod; + return myself; + } + + /** + * Checks if MapStruct is allowed to generate an automatic sub-mapping between {@code sourceType} and {@code + * targetType}. + * This will evaluate to {@code true}, when: + *
  • + *
      Automatic sub-mapping methods generation is not disabled
    + *
      MapStruct is allowed to generate an automatic sub-mapping between the {@code sourceType} and {@code + * targetType}
    + *
  • + * + * @param sourceType candidate source type to generate a sub-mapping from + * @param targetType candidate target type to generate a sub-mapping for + * + * @return {@code true} if MapStruct can try to generate an automatic sub-mapping between the types. + */ + boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType) { + return !isDisableSubMappingMethodsGeneration() && + ctx.canGenerateAutoSubMappingBetween( sourceType, targetType ); + } + + private boolean isDisableSubMappingMethodsGeneration() { + return method.getOptions().getMapper().isDisableSubMappingMethodsGeneration(); + } + + /** + * Creates a forged assignment from the provided {@code sourceRHS} and {@code forgedMethod}. If a mapping method + * for the {@code forgedMethod} already exists, this method will be used for the assignment. + * + * @param sourceRHS that needs to be used for the assignment + * @param forgedMethod the forged method for which we want to create an {@link Assignment} + * + * @return See above + */ + Assignment createForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod) { + + Supplier forgedMappingMethodCreator; + if ( MappingMethodUtils.isEnumMapping( forgedMethod ) ) { + forgedMappingMethodCreator = () -> new ValueMappingMethod.Builder() + .method( forgedMethod ) + .valueMappings( forgedMethod.getOptions().getValueMappings() ) + .enumMapping( forgedMethod.getOptions().getEnumMappingOptions() ) + .mappingContext( ctx ) + .build(); + } + else { + forgedMappingMethodCreator = () -> new BeanMappingMethod.Builder() + .forgedMethod( forgedMethod ) + .mappingContext( ctx ) + .build(); + } + + return getOrCreateForgedAssignment( sourceRHS, forgedMethod, forgedMappingMethodCreator ); + } + + Assignment getOrCreateForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod, + Supplier mappingMethodCreator) { + + if ( ctx.getForgedMethodsUnderCreation().containsKey( forgedMethod ) ) { + return createAssignment( sourceRHS, ctx.getForgedMethodsUnderCreation().get( forgedMethod ) ); + } + else { + ctx.getForgedMethodsUnderCreation().put( forgedMethod, forgedMethod ); + } + + MappingMethod forgedMappingMethod = mappingMethodCreator.get(); + + Assignment forgedAssignment = createForgedAssignment( sourceRHS, forgedMethod, forgedMappingMethod ); + ctx.getForgedMethodsUnderCreation().remove( forgedMethod ); + + return forgedAssignment; + } + + Assignment createForgedAssignment(SourceRHS source, ForgedMethod methodRef, MappingMethod mappingMethod) { + if ( mappingMethod == null ) { + return null; + } + if ( methodRef.getMappingReferences().isRestrictToDefinedMappings() || + !ctx.getMappingsToGenerate().contains( mappingMethod ) ) { + // If the mapping options are restricted only to the defined mappings, then use the mapping method. + // See https://github.com/mapstruct/mapstruct/issues/1148 + ctx.getMappingsToGenerate().add( mappingMethod ); + } + else { + String existingName = ctx.getExistingMappingMethod( mappingMethod ).getName(); + methodRef = new ForgedMethod( existingName, methodRef ); + } + + return createAssignment( source, methodRef ); + } + + private Assignment createAssignment(SourceRHS source, ForgedMethod methodRef) { + Assignment assignment = MethodReference.forForgedMethod( + methodRef, + ParameterBinding.fromParameters( methodRef.getParameters() ) + ); + assignment.setAssignment( source ); + + return assignment; + } + + /** + * Reports that a mapping could not be created. + * + * @param method the method that should be mapped + * @param sourceErrorMessagePart the error message part for the source + * @param sourceType the source type of the mapping + * @param targetType the type of the target mapping + * @param targetPropertyName the name of the target property + */ + void reportCannotCreateMapping(Method method, String sourceErrorMessagePart, Type sourceType, Type targetType, + String targetPropertyName) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.PROPERTYMAPPING_MAPPING_NOT_FOUND, + sourceErrorMessagePart, + targetType.describe(), + targetPropertyName, + targetType.describe(), + sourceType.describe() + ); + } + + /** + * Reports that a mapping could not be created. + * + * @param method the method that should be mapped + * @param posHint hint which @Mapping is the culprit + * @param sourceErrorMessagePart the error message part for the source + * @param sourceType the source type of the mapping + * @param targetType the type of the target mapping + * @param targetPropertyName the name of the target property + */ + void reportCannotCreateMapping(Method method, AnnotationMirror posHint, String sourceErrorMessagePart, + Type sourceType, Type targetType, + String targetPropertyName) { + ctx.getMessager().printMessage( + method.getExecutable(), + posHint, + Message.PROPERTYMAPPING_MAPPING_NOT_FOUND, + sourceErrorMessagePart, + targetType.describe(), + targetPropertyName, + targetType.describe(), + sourceType.describe() + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java new file mode 100644 index 0000000000..c124306105 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -0,0 +1,140 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.SourceRHS; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.util.Strings; + +/** + * An abstract builder that can be reused for building {@link MappingMethod}(s). + * + * @param the builder itself that needs to be used for chaining + * @param the method that the builder builds + * @author Filip Hrisafov + */ +public abstract class AbstractMappingMethodBuilder, + M extends MappingMethod> + extends AbstractBaseBuilder { + + public AbstractMappingMethodBuilder(Class selfType) { + super( selfType ); + } + + private interface ForgeMethodCreator { + ForgedMethod createMethod(String name, Type sourceType, Type returnType, Method basedOn, + ForgedMethodHistory history, boolean forgedNameBased); + + static ForgeMethodCreator forSubclassMapping(MappingReferences mappingReferences) { + return (name, sourceType, targetType, method, description, + forgedNameBased) -> ForgedMethod + .forSubclassMapping( + name, + sourceType, + targetType, + method, + mappingReferences, + description, + forgedNameBased ); + } + } + + public abstract M build(); + + private ForgedMethodHistory description; + + /** + * @return {@code true} if property names should be used for the creation of the {@link ForgedMethodHistory}. + */ + protected abstract boolean shouldUsePropertyNamesInHistory(); + + Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targetType) { + return forgeMapping( sourceRHS, sourceType, targetType, ForgedMethod::forElementMapping ); + } + + Assignment forgeSubclassMapping(SourceRHS sourceRHS, Type sourceType, Type targetType, + MappingReferences mappingReferences) { + return forgeMapping( + sourceRHS, + sourceType, + targetType, + ForgeMethodCreator.forSubclassMapping( mappingReferences ) ); + } + + private Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targetType, + ForgeMethodCreator forgeMethodCreator) { + if ( !canGenerateAutoSubMappingBetween( sourceType, targetType ) ) { + return null; + } + + String name = getName( sourceType, targetType ); + name = Strings.getSafeVariableName( name, ctx.getReservedNames() ); + ForgedMethodHistory history = null; + if ( method instanceof ForgedMethod ) { + history = ( (ForgedMethod) method ).getHistory(); + } + + description = new ForgedMethodHistory( + history, + Strings.stubPropertyName( sourceRHS.getSourceType().getName() ), + Strings.stubPropertyName( targetType.getName() ), + sourceRHS.getSourceType(), + targetType, + shouldUsePropertyNamesInHistory(), + sourceRHS.getSourceErrorMessagePart() ); + + ForgedMethod forgedMethod = + forgeMethodCreator.createMethod( name, sourceType, targetType, method, description, true ); + + return createForgedAssignment( sourceRHS, forgedMethod ); + } + + private String getName(Type sourceType, Type targetType) { + String fromName = getName( sourceType ); + String toName = getName( targetType ); + return Strings.decapitalize( fromName + "To" + toName ); + } + + private String getName(Type type) { + StringBuilder builder = new StringBuilder(); + for ( Type typeParam : type.getTypeParameters() ) { + builder.append( typeParam.getIdentification() ); + } + builder.append( type.getIdentification() ); + return builder.toString(); + } + + public ForgedMethodHistory getDescription() { + return description; + } + + public List getMethodAnnotations() { + if ( method instanceof ForgedMethod ) { + return Collections.emptyList(); + } + AdditionalAnnotationsBuilder additionalAnnotationsBuilder = + new AdditionalAnnotationsBuilder( + ctx.getElementUtils(), + ctx.getTypeFactory(), + ctx.getMessager() ); + List annotations = new ArrayList<>( + additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) + ); + + if ( method.overridesMethod() ) { + annotations.add( new Annotation( ctx.getTypeFactory().getType( Override.class ) ) ); + } + return annotations; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java new file mode 100644 index 0000000000..cb9289e057 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AdditionalAnnotationsBuilder.java @@ -0,0 +1,644 @@ +/* + * 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.model; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Target; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.AnnotateWithGem; +import org.mapstruct.ap.internal.gem.AnnotateWithsGem; +import org.mapstruct.ap.internal.gem.DeprecatedGem; +import org.mapstruct.ap.internal.gem.ElementGem; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; +import org.mapstruct.ap.internal.model.annotation.EnumAnnotationElementHolder; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.RepeatableAnnotations; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; +import org.mapstruct.tools.gem.GemValue; + +import static javax.lang.model.util.ElementFilter.methodsIn; + +/** + * Helper class which is responsible for collecting all additional annotations that should be added. + * + * @author Ben Zegveld + * @since 1.5 + */ +public class AdditionalAnnotationsBuilder + extends RepeatableAnnotations { + private static final String ANNOTATE_WITH_FQN = "org.mapstruct.AnnotateWith"; + private static final String ANNOTATE_WITHS_FQN = "org.mapstruct.AnnotateWiths"; + private TypeFactory typeFactory; + private FormattingMessager messager; + + public AdditionalAnnotationsBuilder(ElementUtils elementUtils, TypeFactory typeFactory, + FormattingMessager messager) { + super( elementUtils, ANNOTATE_WITH_FQN, ANNOTATE_WITHS_FQN ); + this.typeFactory = typeFactory; + this.messager = messager; + } + + @Override + protected AnnotateWithGem singularInstanceOn(Element element) { + return AnnotateWithGem.instanceOn( element ); + } + + @Override + protected AnnotateWithsGem multipleInstanceOn(Element element) { + return AnnotateWithsGem.instanceOn( element ); + } + + @Override + protected void addInstance(AnnotateWithGem gem, Element source, Set mappings) { + buildAnnotation( gem, source ).ifPresent( t -> addAndValidateMapping( mappings, source, gem, t ) ); + } + + @Override + protected void addInstances(AnnotateWithsGem gem, Element source, Set mappings) { + for ( AnnotateWithGem annotateWithGem : gem.value().get() ) { + buildAnnotation( + annotateWithGem, + source ).ifPresent( t -> addAndValidateMapping( mappings, source, annotateWithGem, t ) ); + } + } + + @Override + public Set getProcessedAnnotations(Element source) { + Set processedAnnotations = super.getProcessedAnnotations( source ); + return addDeprecatedAnnotation( source, processedAnnotations ); + } + + private Set addDeprecatedAnnotation(Element source, Set annotations) { + DeprecatedGem deprecatedGem = DeprecatedGem.instanceOn( source ); + if ( deprecatedGem == null ) { + return annotations; + } + Type deprecatedType = typeFactory.getType( Deprecated.class ); + if ( annotations.stream().anyMatch( annotation -> annotation.getType().equals( deprecatedType ) ) ) { + messager.printMessage( + source, + deprecatedGem.mirror(), + Message.ANNOTATE_WITH_DUPLICATE, + deprecatedType.describe() ); + return annotations; + } + List annotationElements = new ArrayList<>(); + if ( deprecatedGem.since() != null && deprecatedGem.since().hasValue() ) { + annotationElements.add( new AnnotationElement( + AnnotationElementType.STRING, + "since", + Collections.singletonList( deprecatedGem.since().getValue() ) + ) ); + } + if ( deprecatedGem.forRemoval() != null && deprecatedGem.forRemoval().hasValue() ) { + annotationElements.add( new AnnotationElement( + AnnotationElementType.BOOLEAN, + "forRemoval", + Collections.singletonList( deprecatedGem.forRemoval().getValue() ) + ) ); + } + annotations.add( new Annotation(deprecatedType, annotationElements ) ); + return annotations; + } + + private void addAndValidateMapping(Set mappings, Element source, AnnotateWithGem gem, Annotation anno) { + if ( anno.getType().getTypeElement().getAnnotation( Repeatable.class ) == null ) { + if ( mappings.stream().anyMatch( existing -> existing.getType().equals( anno.getType() ) ) ) { + messager.printMessage( + source, + gem.mirror(), + Message.ANNOTATE_WITH_ANNOTATION_IS_NOT_REPEATABLE, + anno.getType().describe() ); + return; + } + } + if ( mappings.stream().anyMatch( existing -> { + return existing.getType().equals( anno.getType() ) + && existing.getProperties().equals( anno.getProperties() ); + } ) ) { + messager.printMessage( + source, + gem.mirror(), + Message.ANNOTATE_WITH_DUPLICATE, + anno.getType().describe() ); + return; + } + mappings.add( anno ); + } + + private Optional buildAnnotation(AnnotateWithGem annotationGem, Element element) { + Type annotationType = typeFactory.getType( getTypeMirror( annotationGem.value() ) ); + List eleGems = annotationGem.elements().get(); + if ( isValid( annotationType, eleGems, element, annotationGem.mirror() ) ) { + return Optional.of( new Annotation( annotationType, convertToProperties( eleGems ) ) ); + } + return Optional.empty(); + } + + private List convertToProperties(List eleGems) { + return eleGems.stream().map( gem -> convertToProperty( gem, typeFactory ) ).collect( Collectors.toList() ); + } + + private enum ConvertToProperty { + BOOLEAN( + AnnotationElementType.BOOLEAN, + (eleGem, typeFactory) -> eleGem.booleans().get(), + eleGem -> eleGem.booleans().hasValue() + ), + BYTE( + AnnotationElementType.BYTE, + (eleGem, typeFactory) -> eleGem.bytes().get(), + eleGem -> eleGem.bytes().hasValue() + ), + CHARACTER( + AnnotationElementType.CHARACTER, + (eleGem, typeFactory) -> eleGem.chars().get(), + eleGem -> eleGem.chars().hasValue() + ), + CLASSES( + AnnotationElementType.CLASS, + (eleGem, typeFactory) -> { + return eleGem.classes().get().stream().map( typeFactory::getType ).collect( Collectors.toList() ); + }, + eleGem -> eleGem.classes().hasValue() + ), + DOUBLE( + AnnotationElementType.DOUBLE, + (eleGem, typeFactory) -> eleGem.doubles().get(), + eleGem -> eleGem.doubles().hasValue() + ), + ENUM( + AnnotationElementType.ENUM, + (eleGem, typeFactory) -> { + List values = new ArrayList<>(); + for ( String enumName : eleGem.enums().get() ) { + Type type = typeFactory.getType( eleGem.enumClass().get() ); + values.add( new EnumAnnotationElementHolder( type, enumName ) ); + } + return values; + }, + eleGem -> eleGem.enums().hasValue() && eleGem.enumClass().hasValue() + ), + FLOAT( + AnnotationElementType.FLOAT, + (eleGem, typeFactory) -> eleGem.floats().get(), + eleGem -> eleGem.floats().hasValue() + ), + INT( + AnnotationElementType.INTEGER, + (eleGem, typeFactory) -> eleGem.ints().get(), + eleGem -> eleGem.ints().hasValue() + ), + LONG( + AnnotationElementType.LONG, + (eleGem, typeFactory) -> eleGem.longs().get(), + eleGem -> eleGem.longs().hasValue() + ), + SHORT( + AnnotationElementType.SHORT, + (eleGem, typeFactory) -> eleGem.shorts().get(), + eleGem -> eleGem.shorts().hasValue() + ), + STRING( + AnnotationElementType.STRING, + (eleGem, typeFactory) -> eleGem.strings().get(), + eleGem -> eleGem.strings().hasValue() + ); + + private final AnnotationElementType type; + private final BiFunction> factory; + private final Predicate usabilityChecker; + + ConvertToProperty(AnnotationElementType type, + BiFunction> factory, + Predicate usabilityChecker) { + this.type = type; + this.factory = factory; + this.usabilityChecker = usabilityChecker; + } + + AnnotationElement toProperty(ElementGem eleGem, TypeFactory typeFactory) { + return new AnnotationElement( + type, + eleGem.name().get(), + factory.apply( eleGem, typeFactory ) + ); + } + + boolean isUsable(ElementGem eleGem) { + return usabilityChecker.test( eleGem ); + } + } + + private AnnotationElement convertToProperty(ElementGem eleGem, TypeFactory typeFactory) { + for ( ConvertToProperty convertToJava : ConvertToProperty.values() ) { + if ( convertToJava.isUsable( eleGem ) ) { + return convertToJava.toProperty( eleGem, typeFactory ); + } + } + return null; + } + + private boolean isValid(Type annotationType, List eleGems, Element element, + AnnotationMirror annotationMirror) { + boolean isValid = true; + if ( !annotationIsAllowed( annotationType, element, annotationMirror ) ) { + isValid = false; + } + + List annotationElements = methodsIn( annotationType.getTypeElement() + .getEnclosedElements() ); + if ( !allRequiredElementsArePresent( annotationType, annotationElements, eleGems, element, + annotationMirror ) ) { + isValid = false; + } + if ( !allElementsAreKnownInAnnotation( annotationType, annotationElements, eleGems, element ) ) { + isValid = false; + } + if ( !allElementsAreOfCorrectType( annotationType, annotationElements, eleGems, element ) ) { + isValid = false; + } + if ( !enumConstructionIsCorrectlyUsed( eleGems, element ) ) { + isValid = false; + } + if ( !allElementsAreUnique( eleGems, element ) ) { + isValid = false; + } + return isValid; + } + + private boolean allElementsAreUnique(List eleGems, Element element) { + boolean isValid = true; + List checkedElements = new ArrayList<>(); + for ( ElementGem elementGem : eleGems ) { + String elementName = elementGem.name().get(); + if ( checkedElements.contains( elementName ) ) { + isValid = false; + messager + .printMessage( + element, + elementGem.mirror(), + Message.ANNOTATE_WITH_DUPLICATE_PARAMETER, + elementName ); + } + else { + checkedElements.add( elementName ); + } + } + return isValid; + } + + private boolean enumConstructionIsCorrectlyUsed(List eleGems, Element element) { + boolean isValid = true; + for ( ElementGem elementGem : eleGems ) { + if ( elementGem.enums().hasValue() ) { + if ( elementGem.enumClass().getValue() == null ) { + isValid = false; + messager + .printMessage( + element, + elementGem.mirror(), + Message.ANNOTATE_WITH_ENUM_CLASS_NOT_DEFINED ); + } + else { + Type type = typeFactory.getType( getTypeMirror( elementGem.enumClass() ) ); + if ( type.isEnumType() ) { + List enumConstants = type.getEnumConstants(); + for ( String enumName : elementGem.enums().get() ) { + if ( !enumConstants.contains( enumName ) ) { + isValid = false; + messager + .printMessage( + element, + elementGem.mirror(), + elementGem.enums().getAnnotationValue(), + Message.ANNOTATE_WITH_ENUM_VALUE_DOES_NOT_EXIST, + type.describe(), + enumName ); + } + } + } + } + } + else if ( elementGem.enumClass().getValue() != null ) { + isValid = false; + messager.printMessage( element, elementGem.mirror(), Message.ANNOTATE_WITH_ENUMS_NOT_DEFINED ); + } + } + return isValid; + } + + private boolean annotationIsAllowed(Type annotationType, Element element, AnnotationMirror annotationMirror) { + Target target = annotationType.getTypeElement().getAnnotation( Target.class ); + if ( target == null ) { + return true; + } + + Set annotationTargets = Stream.of( target.value() ) + // The eclipse compiler returns null for some values + // Therefore, we filter out null values + .filter( Objects::nonNull ) + .collect( Collectors.toCollection( () -> EnumSet.noneOf( ElementType.class ) ) ); + + boolean isValid = true; + if ( isTypeTarget( element ) && !annotationTargets.contains( ElementType.TYPE ) ) { + isValid = false; + messager.printMessage( + element, + annotationMirror, + Message.ANNOTATE_WITH_NOT_ALLOWED_ON_CLASS, + annotationType.describe() + ); + } + if ( isMethodTarget( element ) && !annotationTargets.contains( ElementType.METHOD ) ) { + isValid = false; + messager.printMessage( + element, + annotationMirror, + Message.ANNOTATE_WITH_NOT_ALLOWED_ON_METHODS, + annotationType.describe() + ); + } + return isValid; + } + + private boolean isTypeTarget(Element element) { + return element.getKind().isInterface() || element.getKind().isClass(); + } + + private boolean isMethodTarget(Element element) { + return element.getKind() == ElementKind.METHOD; + } + + private boolean allElementsAreKnownInAnnotation(Type annotationType, List annotationParameters, + List eleGems, Element element) { + Set allowedAnnotationParameters = annotationParameters.stream() + .map( ee -> ee.getSimpleName().toString() ) + .collect( Collectors.toSet() ); + boolean isValid = true; + for ( ElementGem eleGem : eleGems ) { + if ( eleGem.name().isValid() + && !allowedAnnotationParameters.contains( eleGem.name().get() ) ) { + isValid = false; + messager + .printMessage( + element, + eleGem.mirror(), + eleGem.name().getAnnotationValue(), + Message.ANNOTATE_WITH_UNKNOWN_PARAMETER, + eleGem.name().get(), + annotationType.describe(), + Strings.getMostSimilarWord( eleGem.name().get(), allowedAnnotationParameters ) + ); + } + } + return isValid; + } + + private boolean allRequiredElementsArePresent(Type annotationType, List annotationParameters, + List elements, Element element, + AnnotationMirror annotationMirror) { + + boolean valid = true; + for ( ExecutableElement annotationParameter : annotationParameters ) { + if ( annotationParameter.getDefaultValue() == null ) { + // Mandatory parameter, must be present in the elements + String parameterName = annotationParameter.getSimpleName().toString(); + boolean elementGemDefined = false; + for ( ElementGem elementGem : elements ) { + if ( elementGem.isValid() && elementGem.name().get().equals( parameterName ) ) { + elementGemDefined = true; + break; + } + } + + if ( !elementGemDefined ) { + valid = false; + messager + .printMessage( + element, + annotationMirror, + Message.ANNOTATE_WITH_MISSING_REQUIRED_PARAMETER, + parameterName, + annotationType.describe() + ); + } + } + } + + + return valid; + } + + private boolean allElementsAreOfCorrectType(Type annotationType, List annotationParameters, + List elements, + Element element) { + Map annotationParametersByName = + annotationParameters.stream() + .collect( Collectors.toMap( ee -> ee.getSimpleName().toString(), Function.identity() ) ); + boolean isValid = true; + for ( ElementGem eleGem : elements ) { + Type annotationParameterType = getAnnotationParameterType( annotationParametersByName, eleGem ); + Type annotationParameterTypeSingular = getNonArrayType( annotationParameterType ); + if ( annotationParameterTypeSingular == null ) { + continue; + } + if ( hasTooManyDifferentTypes( eleGem ) ) { + isValid = false; + messager.printMessage( + element, + eleGem.mirror(), + eleGem.name().getAnnotationValue(), + Message.ANNOTATE_WITH_TOO_MANY_VALUE_TYPES, + eleGem.name().get(), + annotationParameterType.describe(), + annotationType.describe() + ); + } + else { + Map elementTypes = getParameterTypes( eleGem ); + Set reportedSizeError = new HashSet<>(); + for ( Type eleGemType : elementTypes.keySet() ) { + if ( !sameTypeOrAssignableClass( annotationParameterTypeSingular, eleGemType ) ) { + isValid = false; + messager.printMessage( + element, + eleGem.mirror(), + eleGem.name().getAnnotationValue(), + Message.ANNOTATE_WITH_WRONG_PARAMETER, + eleGem.name().get(), + eleGemType.describe(), + annotationParameterType.describe(), + annotationType.describe() + ); + } + else if ( !annotationParameterType.isArrayType() + && elementTypes.get( eleGemType ) > 1 + && !reportedSizeError.contains( eleGem ) ) { + isValid = false; + messager.printMessage( + element, + eleGem.mirror(), + Message.ANNOTATE_WITH_PARAMETER_ARRAY_NOT_EXPECTED, + eleGem.name().get(), + annotationType.describe() + ); + reportedSizeError.add( eleGem ); + } + } + } + } + return isValid; + } + + private boolean hasTooManyDifferentTypes( ElementGem eleGem ) { + return Arrays.stream( ConvertToProperty.values() ) + .filter( anotationElement -> anotationElement.isUsable( eleGem ) ) + .count() > 1; + } + + private Type getNonArrayType(Type annotationParameterType) { + if ( annotationParameterType == null ) { + return null; + } + if ( annotationParameterType.isArrayType() ) { + return annotationParameterType.getComponentType(); + } + + return annotationParameterType; + } + + private boolean sameTypeOrAssignableClass(Type annotationParameterType, Type eleGemType) { + return annotationParameterType.equals( eleGemType ) + || eleGemType.isAssignableTo( getTypeBound( annotationParameterType ) ); + } + + private Type getTypeBound(Type annotationParameterType) { + List typeParameters = annotationParameterType.getTypeParameters(); + if ( typeParameters.size() != 1 ) { + return annotationParameterType; + } + return typeParameters.get( 0 ).getTypeBound(); + } + + private Map getParameterTypes(ElementGem eleGem) { + Map suppliedParameterTypes = new HashMap<>(); + if ( eleGem.booleans().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( boolean.class ), + eleGem.booleans().get().size() ); + } + if ( eleGem.bytes().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( byte.class ), + eleGem.bytes().get().size() ); + } + if ( eleGem.chars().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( char.class ), + eleGem.chars().get().size() ); + } + if ( eleGem.classes().hasValue() ) { + for ( TypeMirror mirror : eleGem.classes().get() ) { + suppliedParameterTypes.put( + typeFactory.getType( typeMirrorFromAnnotation( mirror ) ), + eleGem.classes().get().size() + ); + } + } + if ( eleGem.doubles().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( double.class ), + eleGem.doubles().get().size() ); + } + if ( eleGem.floats().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( float.class ), + eleGem.floats().get().size() ); + } + if ( eleGem.ints().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( int.class ), + eleGem.ints().get().size() ); + } + if ( eleGem.longs().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( long.class ), + eleGem.longs().get().size() ); + } + if ( eleGem.shorts().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( short.class ), + eleGem.shorts().get().size() ); + } + if ( eleGem.strings().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( String.class ), + eleGem.strings().get().size() ); + } + if ( eleGem.enums().hasValue() && eleGem.enumClass().hasValue() ) { + suppliedParameterTypes.put( + typeFactory.getType( getTypeMirror( eleGem.enumClass() ) ), + eleGem.enums().get().size() ); + } + return suppliedParameterTypes; + } + + private Type getAnnotationParameterType(Map annotationParameters, + ElementGem element) { + if ( annotationParameters.containsKey( element.name().get() ) ) { + return typeFactory.getType( annotationParameters.get( element.name().get() ).getReturnType() ); + } + else { + return null; + } + } + + private TypeMirror getTypeMirror(GemValue gemValue) { + return typeMirrorFromAnnotation( gemValue.getValue() ); + } + + private TypeMirror typeMirrorFromAnnotation(TypeMirror typeMirror) { + if ( typeMirror == null ) { + // When a class used in an annotation is created by another annotation processor + // then javac will not return correct TypeMirror with TypeKind#ERROR, but rather a string "" + // the gem tools would return a null TypeMirror in that case. + // Therefore, throw TypeHierarchyErroneousException so we can postpone the generation of the mapper + throw new TypeHierarchyErroneousException( typeMirror ); + } + + return typeMirror; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java new file mode 100644 index 0000000000..889d602cdb --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java @@ -0,0 +1,106 @@ +/* + * 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.model; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Represents a constructor that is used for constructor injection. + * + * @author Kevin Grüneberg + */ +public class AnnotatedConstructor extends ModelElement implements Constructor { + + private String name; + private final List mapperReferences; + private final List annotations; + private final NoArgumentConstructor noArgumentConstructor; + private final Set fragments; + + public static AnnotatedConstructor forComponentModels(String name, + List mapperReferences, + List annotations, + Constructor constructor, + boolean includeNoArgConstructor) { + + NoArgumentConstructor noArgumentConstructor = null; + if ( constructor instanceof NoArgumentConstructor ) { + noArgumentConstructor = (NoArgumentConstructor) constructor; + } + NoArgumentConstructor noArgConstructorToBeIncluded = null; + Set fragmentsToBeIncluded = Collections.emptySet(); + if ( includeNoArgConstructor ) { + if ( noArgumentConstructor != null ) { + noArgConstructorToBeIncluded = noArgumentConstructor; + } + else { + noArgConstructorToBeIncluded = new NoArgumentConstructor( name, fragmentsToBeIncluded ); + } + } + else if ( noArgumentConstructor != null ) { + fragmentsToBeIncluded = noArgumentConstructor.getFragments(); + } + return new AnnotatedConstructor( + name, + mapperReferences, + annotations, + noArgConstructorToBeIncluded, + fragmentsToBeIncluded + ); + } + + private AnnotatedConstructor(String name, List mapperReferences, + List annotations, NoArgumentConstructor noArgumentConstructor, + Set fragments) { + this.name = name; + this.mapperReferences = mapperReferences; + this.annotations = annotations; + this.noArgumentConstructor = noArgumentConstructor; + this.fragments = fragments; + } + + @Override + public Set getImportTypes() { + Set types = new HashSet<>(); + + for ( MapperReference mapperReference : mapperReferences ) { + types.addAll( mapperReference.getImportTypes() ); + } + + for ( Annotation annotation : annotations ) { + types.addAll( annotation.getImportTypes() ); + } + + return types; + } + + @Override + public String getName() { + return name; + } + + public List getMapperReferences() { + return mapperReferences; + } + + public List getAnnotations() { + return annotations; + } + + public NoArgumentConstructor getNoArgumentConstructor() { + return noArgumentConstructor; + } + + public Set getFragments() { + return fragments; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.java new file mode 100644 index 0000000000..8f60b052b9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedSetter.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.ap.internal.model; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Type; + +/** + * A method in a generated type that represents a setter with annotations. + * + * @author Lucas Resch + */ +public class AnnotatedSetter extends GeneratedTypeMethod { + + private final Field field; + private final Collection methodAnnotations; + private final Collection parameterAnnotations; + + public AnnotatedSetter(Field field, Collection methodAnnotations, + Collection parameterAnnotations) { + this.field = field; + this.methodAnnotations = methodAnnotations; + this.parameterAnnotations = parameterAnnotations; + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>( field.getImportTypes() ); + for ( Annotation annotation : methodAnnotations ) { + importTypes.addAll( annotation.getImportTypes() ); + } + + for ( Annotation annotation : parameterAnnotations ) { + importTypes.addAll( annotation.getImportTypes() ); + } + + return importTypes; + } + + public Type getType() { + return field.getType(); + } + + public String getFieldName() { + return field.getVariableName(); + } + + public Collection getMethodAnnotations() { + return methodAnnotations; + } + + public Collection getParameterAnnotations() { + return parameterAnnotations; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java index b3ea9d40c2..866012c51a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Annotation.java @@ -1,27 +1,16 @@ -/** - * Copyright 2012-2015 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.model; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Type; @@ -34,16 +23,13 @@ public class Annotation extends ModelElement { private final Type type; - /** - * List of annotation attributes. Quite simplistic, but it's sufficient for now. - */ - private List properties; + private List properties; public Annotation(Type type) { - this( type, Collections.emptyList() ); + this( type, Collections.emptyList() ); } - public Annotation(Type type, List properties) { + public Annotation(Type type, List properties) { this.type = type; this.properties = properties; } @@ -54,10 +40,15 @@ public Type getType() { @Override public Set getImportTypes() { - return Collections.singleton( type ); + Set types = new HashSet<>(); + for ( AnnotationElement prop : properties ) { + types.addAll( prop.getImportTypes() ); + } + types.add( type ); + return types; } - public List getProperties() { + public List getProperties() { return properties; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java index 518b5394ae..4cb78ea4f8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotationMapperReference.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model; @@ -25,18 +12,28 @@ import org.mapstruct.ap.internal.model.common.Type; /** - * Mapper reference which is retrieved via Annotation-based dependency injection. + * Mapper reference which is retrieved via Annotation-based dependency injection.
    + * The dependency injection may vary between field and constructor injection. Thus, it is possible to define, whether to + * include annotations on the field. * * @author Gunnar Morling * @author Andreas Gudian + * @author Kevin Grüneberg */ public class AnnotationMapperReference extends MapperReference { private final List annotations; - public AnnotationMapperReference(Type type, String variableName, List annotations, boolean isUsed) { + private final boolean fieldFinal; + + private final boolean includeAnnotationsOnField; + + public AnnotationMapperReference(Type type, String variableName, List annotations, boolean isUsed, + boolean fieldFinal, boolean includeAnnotationsOnField) { super( type, variableName, isUsed ); this.annotations = annotations; + this.fieldFinal = fieldFinal; + this.includeAnnotationsOnField = includeAnnotationsOnField; } public List getAnnotations() { @@ -45,7 +42,7 @@ public List getAnnotations() { @Override public Set getImportTypes() { - Set types = new HashSet(); + Set types = new HashSet<>(); types.add( getType() ); for ( Annotation annotation : annotations ) { @@ -54,4 +51,22 @@ public Set getImportTypes() { return types; } + + public boolean isFieldFinal() { + return fieldFinal; + } + + public boolean isIncludeAnnotationsOnField() { + return includeAnnotationsOnField; + } + + public AnnotationMapperReference withNewAnnotations(List annotations) { + return new AnnotationMapperReference( + getType(), + getVariableName(), + annotations, + isUsed(), + isFieldFinal(), + isIncludeAnnotationsOnField() ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/AssignmentFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/AssignmentFactory.java deleted file mode 100644 index 01dbe78389..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AssignmentFactory.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model; - -import java.util.List; -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.common.Type; -import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; - -/** - * Factory class for creating all types of assignments - * - * @author Sjaak Derksen - */ -public class AssignmentFactory { - - private AssignmentFactory() { - } - - public static Assignment createTypeConversion(Set importTypes, List exceptionTypes, String expression) { - return new TypeConversion( importTypes, exceptionTypes, expression ); - } - - public static Assignment createMethodReference(Method method, MapperReference declaringMapper, - Type targetType) { - return new MethodReference( method, declaringMapper, targetType ); - } - - public static Assignment createMethodReference(BuiltInMethod method, ConversionContext contextParam) { - return new MethodReference( method, contextParam ); - } - - public static Direct createDirect(String sourceRef) { - return new Direct( sourceRef ); - } -} - diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index 2843ced284..080eab1e76 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model; @@ -27,34 +14,73 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; - +import java.util.function.Supplier; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; import javax.tools.Diagnostic; +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; import org.mapstruct.ap.internal.model.PropertyMapping.ConstantMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.JavaExpressionMappingBuilder; import org.mapstruct.ap.internal.model.PropertyMapping.PropertyMappingBuilder; +import org.mapstruct.ap.internal.model.beanmapping.MappingReference; +import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; +import org.mapstruct.ap.internal.model.beanmapping.SourceReference; +import org.mapstruct.ap.internal.model.beanmapping.TargetReference; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.BuilderType; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.model.common.NewInstanceCreation; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder; -import org.mapstruct.ap.internal.model.source.Mapping; +import org.mapstruct.ap.internal.model.source.BeanMappingOptions; +import org.mapstruct.ap.internal.model.source.MappingOptions; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.model.source.SourceReference; -import org.mapstruct.ap.internal.option.ReportingPolicy; -import org.mapstruct.ap.internal.prism.BeanMappingPrism; -import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; -import org.mapstruct.ap.internal.util.Executables; -import org.mapstruct.ap.internal.util.MapperConfiguration; +import org.mapstruct.ap.internal.model.source.SubclassMappingOptions; +import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import org.mapstruct.ap.internal.util.kotlin.KotlinMetadata; + +import static org.mapstruct.ap.internal.model.beanmapping.MappingReferences.forSourceMethod; +import static org.mapstruct.ap.internal.util.Collections.first; +import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_ABSTRACT; +import static org.mapstruct.ap.internal.util.Message.BEANMAPPING_NOT_ASSIGNABLE; +import static org.mapstruct.ap.internal.util.Message.GENERAL_ABSTRACT_RETURN_TYPE; +import static org.mapstruct.ap.internal.util.Message.GENERAL_AMBIGUOUS_CONSTRUCTORS; +import static org.mapstruct.ap.internal.util.Message.GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS; +import static org.mapstruct.ap.internal.util.Message.PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET; +import static org.mapstruct.ap.internal.util.Message.PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PROPERTY_FROM_TARGET; /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one bean type to another, optionally @@ -62,126 +88,714 @@ * * @author Gunnar Morling */ -public class BeanMappingMethod extends MappingMethod { +public class BeanMappingMethod extends NormalTypeMappingMethod { private final List propertyMappings; private final Map> mappingsByParameter; + private final Map> constructorMappingsByParameter; + private final Map presenceChecksByParameter; private final List constantMappings; - private final MethodReference factoryMethod; - private final boolean mapNullToDefault; - private final Type resultType; - - public static class Builder { - - private MappingBuilderContext ctx; - private SourceMethod method; - private Map unprocessedTargetProperties; + private final List constructorConstantMappings; + private final List subclassMappings; + private final Type returnTypeToConstruct; + private final NewInstanceCreation newInstance; + private final BuilderType returnTypeBuilder; + private final MethodReference finalizerMethod; + private final String finalizedResultName; + private final String optionalResultName; + private final List beforeMappingReferencesWithFinalizedReturnType; + private final List afterMappingReferencesWithFinalizedReturnType; + private final List afterMappingReferencesWithOptionalReturnType; + private final Type subclassExhaustiveException; + private final Map sourceParametersReassignments; + + private final MappingReferences mappingReferences; + + public static class Builder extends AbstractMappingMethodBuilder { + + /* returnType to construct can have a builder */ + private BuilderType returnTypeBuilder; + private Map unprocessedConstructorProperties; + private Map unprocessedTargetProperties; + private Map unprocessedSourceProperties; + private Set missingIgnoredSourceProperties; + private Set redundantIgnoredSourceProperties; private Set targetProperties; - private final List propertyMappings = new ArrayList(); - private final Set unprocessedSourceParameters = new HashSet(); - private List qualifiers; - private NullValueMappingStrategyPrism nullValueMappingStrategy; - private TypeMirror resultTypeMirror; - private final Set existingVariableNames = new HashSet(); - - public Builder mappingContext(MappingBuilderContext mappingContext) { - this.ctx = mappingContext; - return this; + private final List propertyMappings = new ArrayList<>(); + private final Set unprocessedSourceParameters = new HashSet<>(); + private final Set existingVariableNames = new HashSet<>(); + private final Map> unprocessedDefinedTargets = new LinkedHashMap<>(); + private final Map sourceParametersReassignments = new HashMap<>(); + + private MappingReferences mappingReferences; + private List targetThisReferences; + private MethodReference factoryMethod; + private boolean hasFactoryMethod; + + public Builder() { + super( Builder.class ); } - public Builder souceMethod(SourceMethod sourceMethod) { - this.method = sourceMethod; - CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy(); - Map accessors = method.getResultType().getPropertyWriteAccessors( cms ); - this.targetProperties = accessors.keySet(); - this.unprocessedTargetProperties = new LinkedHashMap( accessors ); - for ( Parameter sourceParameter : method.getSourceParameters() ) { - unprocessedSourceParameters.add( sourceParameter ); - } - existingVariableNames.addAll( method.getParameterNames() ); - return this; + @Override + protected boolean shouldUsePropertyNamesInHistory() { + return true; } - public Builder qualifiers(List qualifiers) { - this.qualifiers = qualifiers; + public Builder sourceMethod(SourceMethod sourceMethod) { + method( sourceMethod ); return this; } - public Builder nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) { - this.nullValueMappingStrategy = nullValueMappingStrategy; - return this; - } - - public Builder resultType(TypeMirror resultType) { - this.resultTypeMirror = resultType; + public Builder forgedMethod(ForgedMethod forgedMethod) { + method( forgedMethod ); + mappingReferences = forgedMethod.getMappingReferences(); + Parameter sourceParameter = first( Parameter.getSourceParameters( forgedMethod.getParameters() ) ); + for ( MappingReference mappingReference : mappingReferences.getMappingReferences() ) { + SourceReference sourceReference = mappingReference.getSourceReference(); + if ( sourceReference != null ) { + mappingReference.setSourceReference( new SourceReference.BuilderFromSourceReference() + .sourceParameter( sourceParameter ) + .sourceReference( sourceReference ) + .build() ); + } + } return this; } + @Override public BeanMappingMethod build() { - // map properties with mapping - boolean mappingErrorOccured = handleDefinedSourceMappings(); - if ( mappingErrorOccured ) { + + BeanMappingOptions beanMapping = method.getOptions().getBeanMapping(); + SelectionParameters selectionParameters = beanMapping != null ? beanMapping.getSelectionParameters() : null; + + /* the return type that needs to be constructed (new or factorized), so for instance: */ + /* 1) the return type of a non-update method */ + /* 2) or the implementation type that needs to be used when the return type is abstract */ + /* 3) or the builder whenever the return type is immutable */ + Type returnTypeToConstruct = null; + + // determine which return type to construct + boolean cannotConstructReturnType = false; + if ( !method.getReturnType().isVoid() ) { + BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); + Type returnTypeImpl; + Type userDefinedReturnType = null; + if ( selectionParameters != null && selectionParameters.getResultType() != null ) { + // This is a user-defined return type, which means we need to do some extra checks for it + userDefinedReturnType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ); + returnTypeImpl = userDefinedReturnType; + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( userDefinedReturnType, builder ); + } + else { + Type methodReturnType = method.getReturnType(); + if ( methodReturnType.isOptionalType() ) { + returnTypeImpl = methodReturnType.getOptionalBaseType(); + } + else { + returnTypeImpl = methodReturnType; + } + returnTypeBuilder = ctx.getTypeFactory().builderTypeFor( returnTypeImpl, builder ); + } + if ( isBuilderRequired() ) { + // the userDefinedReturn type can also require a builder. That buildertype is already set + returnTypeImpl = returnTypeBuilder.getBuilder(); + initializeFactoryMethod( returnTypeImpl, selectionParameters ); + if ( factoryMethod != null + || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl ) + || doesNotAllowAbstractReturnTypeAndCanBeConstructed( returnTypeImpl ) ) { + returnTypeToConstruct = returnTypeImpl; + } + else { + cannotConstructReturnType = true; + } + } + else if ( userDefinedReturnType != null ) { + initializeFactoryMethod( returnTypeImpl, selectionParameters ); + if ( factoryMethod != null || canResultTypeFromBeanMappingBeConstructed( returnTypeImpl ) ) { + returnTypeToConstruct = returnTypeImpl; + } + else { + cannotConstructReturnType = true; + } + } + else if ( !method.isUpdateMethod() ) { + initializeFactoryMethod( returnTypeImpl, selectionParameters ); + if ( factoryMethod != null + || allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed( returnTypeImpl ) + || doesNotAllowAbstractReturnTypeAndCanBeConstructed( returnTypeImpl ) ) { + returnTypeToConstruct = returnTypeImpl; + } + else { + cannotConstructReturnType = true; + } + } + } + + if ( cannotConstructReturnType ) { + // If the return type cannot be constructed then no need to try to create mappings return null; } - // map properties without a mapping - applyPropertyNameBasedMapping(); + /* the type that needs to be used in the mapping process as target */ + Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct; - // map parameters without a mapping - applyParameterNameBasedMapping(); + existingVariableNames.addAll( method.getParameterNames() ); - // report errors on unmapped properties - reportErrorForUnmappedTargetPropertiesIfRequired(); + CollectionMappingStrategyGem cms = this.method.getOptions().getMapper().getCollectionMappingStrategy(); - // mapNullToDefault - boolean mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy ); + // determine accessors + Map accessors = resultTypeToMap.getPropertyWriteAccessors( cms ); + this.targetProperties = new LinkedHashSet<>( accessors.keySet() ); + this.unprocessedTargetProperties = new LinkedHashMap<>( accessors ); - BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); + boolean constructorAccessorHadError = false; + if ( !method.isUpdateMethod() && !hasFactoryMethod ) { + ConstructorAccessor constructorAccessor = getConstructorAccessor( resultTypeToMap ); + if ( constructorAccessor != null && !constructorAccessor.hasError ) { - MethodReference factoryMethod = null; - if ( !method.isUpdateMethod() ) { - factoryMethod = ctx.getMappingResolver().getFactoryMethod( - method, - method.getResultType(), - qualifiers, - resultTypeMirror ); + this.unprocessedConstructorProperties = constructorAccessor.constructorAccessors; + + factoryMethod = MethodReference.forConstructorInvocation( + resultTypeToMap, + constructorAccessor.parameterBindings + ); + + } + else { + this.unprocessedConstructorProperties = new LinkedHashMap<>(); + } + constructorAccessorHadError = constructorAccessor != null && constructorAccessor.hasError; + + this.targetProperties.addAll( this.unprocessedConstructorProperties.keySet() ); + + this.unprocessedTargetProperties.putAll( this.unprocessedConstructorProperties ); + } + else { + unprocessedConstructorProperties = new LinkedHashMap<>(); } - // if there's no factory method, try the resultType in the @BeanMapping - Type resultType = null; - if ( factoryMethod == null ) { - if ( resultTypeMirror != null ) { - resultType = ctx.getTypeFactory().getType( resultTypeMirror ); - if ( !resultType.isAssignableTo( method.getResultType() ) ) { - ctx.getMessager().printMessage( - method.getExecutable(), - beanMappingPrism.mirror, - Message.BEANMAPPING_NOT_ASSIGNABLE, resultType, method.getResultType() - ); + this.unprocessedSourceProperties = new LinkedHashMap<>(); + for ( Parameter sourceParameter : method.getSourceParameters() ) { + unprocessedSourceParameters.add( sourceParameter ); + + Type sourceParameterType = sourceParameter.getType(); + if ( sourceParameterType.isOptionalType() ) { + String sourceParameterValueName = Strings.getSafeVariableName( + sourceParameter.getName() + "Value", + existingVariableNames + ); + existingVariableNames.add( sourceParameterValueName ); + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParametersReassignments.put( + sourceParameter.getName(), + new Parameter( sourceParameterValueName, sourceParameter.getName(), sourceParameterType ) + ); + } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() || + sourceParameterType.isMapType() ) { + continue; + } + + Map readAccessors = sourceParameterType.getPropertyReadAccessors(); + + unprocessedSourceProperties.putAll( readAccessors ); + } + + // get bean mapping (when specified as annotation ) + this.missingIgnoredSourceProperties = new LinkedHashSet<>(); + this.redundantIgnoredSourceProperties = new LinkedHashSet<>(); + if ( beanMapping != null && !beanMapping.getIgnoreUnmappedSourceProperties().isEmpty() ) { + // Get source properties explicitly mapped using @Mapping annotations + Set mappedSourceProperties = method.getOptions().getMappings().stream() + .map( MappingOptions::getSourceName ) + .filter( Objects::nonNull ) + .collect( Collectors.toSet() ); + + for ( String ignoreUnmapped : beanMapping.getIgnoreUnmappedSourceProperties() ) { + // Track missing ignored properties (i.e. not in source class) + if ( unprocessedSourceProperties.remove( ignoreUnmapped ) == null ) { + missingIgnoredSourceProperties.add( ignoreUnmapped ); + } + // Track redundant ignored properties (actually mapped despite being ignored) + if ( mappedSourceProperties.contains( ignoreUnmapped ) ) { + redundantIgnoredSourceProperties.add( ignoreUnmapped ); } } } + initializeMappingReferencesIfNeeded( resultTypeToMap ); + + boolean shouldHandledDefinedMappings = shouldHandledDefinedMappings( resultTypeToMap ); + + + if ( shouldHandledDefinedMappings ) { + // map properties with mapping + boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap ); + if ( mappingErrorOccurred ) { + return null; + } + } + + // If defined mappings should not be handled then we should not apply implicit mappings + boolean applyImplicitMappings = + shouldHandledDefinedMappings && !mappingReferences.isRestrictToDefinedMappings(); + if ( applyImplicitMappings ) { + applyImplicitMappings = beanMapping == null || !beanMapping.isIgnoredByDefault(); + } + if ( applyImplicitMappings ) { + + // apply name based mapping from a source reference + applyTargetThisMapping(); + + // map properties without a mapping + applyPropertyNameBasedMapping(); + + // map parameters without a mapping + applyParameterNameBasedMapping(); + + } + + // Process the unprocessed defined targets + handleUnprocessedDefinedTargets(); + + // Initialize unprocessed constructor properties + handleUnmappedConstructorProperties(); + + // report errors on unmapped properties + if ( shouldHandledDefinedMappings ) { + reportErrorForUnmappedTargetPropertiesIfRequired( resultTypeToMap, constructorAccessorHadError ); + reportErrorForUnmappedSourcePropertiesIfRequired(); + } + reportErrorForMissingIgnoredSourceProperties(); + reportErrorForUnusedSourceParameters(); + reportErrorForRedundantIgnoredSourceProperties(); + + // mapNullToDefault — JSpecify @NonNull return forces RETURN_DEFAULT to avoid generating `return null`. + boolean mapNullToDefault = method.getOptions() + .getBeanMapping() + .getNullValueMappingStrategy() + .isReturnDefault(); + + // sort sortPropertyMappingsByDependencies(); - List beforeMappingMethods = - LifecycleCallbackFactory.beforeMappingMethods( method, qualifiers, ctx ); - List afterMappingMethods = - LifecycleCallbackFactory.afterMappingMethods( method, qualifiers, ctx ); + // before / after mappings + List beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods( + method, + resultTypeToMap, + selectionParameters, + ctx, + existingVariableNames + ); + + Supplier> additionalAfterMappingParameterBindingsProvider = () -> + sourceParametersReassignments.values() + .stream() + .map( parameter -> method.getSourceParameters().size() == 1 ? + ParameterBinding.fromParameter( parameter ) : + ParameterBinding.fromTypeAndName( + parameter.getType(), + parameter.getOriginalName() + ".get()" + ) ) + .collect( Collectors.toList() ); + List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( + method, + resultTypeToMap, + selectionParameters, + ctx, + existingVariableNames, + Collections::emptyList + ); + + if ( method instanceof ForgedMethod ) { + ForgedMethod forgedMethod = (ForgedMethod) method; + if ( factoryMethod != null ) { + forgedMethod.addThrownTypes( factoryMethod.getThrownTypes() ); + } + for ( LifecycleCallbackMethodReference beforeMappingMethod : beforeMappingMethods ) { + forgedMethod.addThrownTypes( beforeMappingMethod.getThrownTypes() ); + } + + for ( LifecycleCallbackMethodReference afterMappingMethod : afterMappingMethods ) { + forgedMethod.addThrownTypes( afterMappingMethod.getThrownTypes() ); + } + + + for ( PropertyMapping propertyMapping : propertyMappings ) { + if ( propertyMapping.getAssignment() != null ) { + forgedMethod.addThrownTypes( propertyMapping.getAssignment().getThrownTypes() ); + } + } + + } + + TypeMirror subclassExhaustiveException = method.getOptions() + .getBeanMapping() + .getSubclassExhaustiveException(); + Type subclassExhaustiveExceptionType = ctx.getTypeFactory().getType( subclassExhaustiveException ); + + List subclasses = new ArrayList<>(); + for ( SubclassMappingOptions subclassMappingOptions : method.getOptions().getSubclassMappings() ) { + subclasses.add( createSubclassMapping( subclassMappingOptions ) ); + } + + MethodReference finalizeMethod = null; + + List beforeMappingReferencesWithFinalizedReturnType = new ArrayList<>(); + List afterMappingReferencesWithFinalizedReturnType = new ArrayList<>(); + if ( shouldCallFinalizerMethod( returnTypeToConstruct ) ) { + finalizeMethod = getFinalizerMethod(); + + Type finalizerReturnType = method.getReturnType(); + if ( finalizerReturnType.isOptionalType() ) { + finalizerReturnType = finalizerReturnType.getOptionalBaseType(); + } + + beforeMappingReferencesWithFinalizedReturnType.addAll( filterMappingTarget( + LifecycleMethodResolver.beforeMappingMethods( + method, + finalizerReturnType, + selectionParameters, + ctx, + existingVariableNames + ), + false + ) ); + + afterMappingReferencesWithFinalizedReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( + method, + finalizerReturnType, + selectionParameters, + ctx, + existingVariableNames, + additionalAfterMappingParameterBindingsProvider + ) ); + + keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, finalizerReturnType ); + keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, finalizerReturnType ); + } + + List afterMappingReferencesWithOptionalReturnType = new ArrayList<>(); + if ( method.getReturnType().isOptionalType() ) { + afterMappingReferencesWithOptionalReturnType.addAll( LifecycleMethodResolver.afterMappingMethods( + method, + method.getReturnType(), + selectionParameters, + ctx, + existingVariableNames, + additionalAfterMappingParameterBindingsProvider + ) ); + + keepMappingReferencesUsingTarget( + afterMappingReferencesWithOptionalReturnType, + method.getReturnType() + ); + + } + + Map presenceChecksByParameter = new LinkedHashMap<>(); + for ( Parameter sourceParameter : method.getSourceParameters() ) { + PresenceCheck parameterPresenceCheck = PresenceCheckMethodResolver.getPresenceCheckForSourceParameter( + method, + selectionParameters, + sourceParameter, + ctx + ); + if ( parameterPresenceCheck != null ) { + presenceChecksByParameter.put( sourceParameter.getName(), parameterPresenceCheck ); + } + } + + // JSpecify: a @NonNull return forces RETURN_DEFAULT to avoid generating `return null`. + // The extra presence-check guard is bean-specific and required for correctness, not just an + // optimization: the bean template only emits a `return null` (and the presence-check wrapping that + // forcing mapNullToDefault would alter) when there are nullable source parameters. With none, + // getPresenceCheckByParameter would resolve to null in the single-source template branches. + // Container/Map/Stream methods instead gate this in their templates via `sourceParameterPresenceCheck??`, + // so they force unconditionally. + if ( !mapNullToDefault + && !presenceChecksByParameter.isEmpty() + && ctx.isJSpecifyNonNullReturn( method ) ) { + ctx.getMessager().note( 2, + Message.MAPPING_METHOD_JSPECIFY_FORCE_RETURN_DEFAULT, + method.getName() ); + mapNullToDefault = true; + } return new BeanMappingMethod( method, + getMethodAnnotations(), + existingVariableNames, propertyMappings, factoryMethod, mapNullToDefault, - resultType, - existingVariableNames, + returnTypeToConstruct, + returnTypeBuilder, beforeMappingMethods, - afterMappingMethods + afterMappingMethods, + beforeMappingReferencesWithFinalizedReturnType, + afterMappingReferencesWithFinalizedReturnType, + afterMappingReferencesWithOptionalReturnType, + finalizeMethod, + mappingReferences, + subclasses, + presenceChecksByParameter, + subclassExhaustiveExceptionType, + sourceParametersReassignments ); } + private void keepMappingReferencesUsingTarget(List references, Type type) { + references.removeIf( reference -> { + List bindings = reference.getParameterBindings(); + if ( bindings.isEmpty() ) { + return true; + } + for ( ParameterBinding binding : bindings ) { + if ( binding.isMappingTarget() ) { + if ( type.isAssignableTo( binding.getType() ) ) { + // If the mapping target matches the type then we need to keep this + return false; + } + } + else if ( binding.isTargetType() ) { + Type targetType = binding.getType(); + List targetTypeTypeParameters = targetType.getTypeParameters(); + if ( targetTypeTypeParameters.size() == 1 ) { + if ( type.isAssignableTo( targetTypeTypeParameters.get( 0 ) ) ) { + return false; + } + } + } + } + + return true; + } ); + } + + private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) { + return !isAbstractReturnTypeAllowed() + && canReturnTypeBeConstructed( returnTypeImpl ); + } + + private boolean allowsAbstractReturnTypeAndIsEitherAbstractOrCanBeConstructed(Type returnTypeImpl) { + return isAbstractReturnTypeAllowed() + && isReturnTypeAbstractOrCanBeConstructed( returnTypeImpl ); + } + + private SubclassMapping createSubclassMapping(SubclassMappingOptions subclassMappingOptions) { + TypeFactory typeFactory = ctx.getTypeFactory(); + Type sourceType = typeFactory.getType( subclassMappingOptions.getSource() ); + Type targetType = typeFactory.getType( subclassMappingOptions.getTarget() ); + + SourceRHS rightHandSide = new SourceRHS( + "subclassMapping", + sourceType, + Collections.emptySet(), + "SubclassMapping for " + sourceType.getFullyQualifiedName() ); + SelectionCriteria criteria = + SelectionCriteria + .forSubclassMappingMethods( + subclassMappingOptions.getSelectionParameters().withSourceRHS( rightHandSide ), + subclassMappingOptions.getMappingControl( ctx.getElementUtils() ) + ); + Assignment assignment = ctx + .getMappingResolver() + .getTargetAssignment( + method, + null, + targetType, + FormattingParameters.EMPTY, + criteria, + rightHandSide, + subclassMappingOptions.getMirror(), + () -> forgeSubclassMapping( + rightHandSide, + sourceType, + targetType, + mappingReferences ) ); + String sourceArgument = null; + for ( Parameter parameter : method.getSourceParameters() ) { + if ( ctx + .getTypeUtils() + .isAssignable( sourceType.getTypeMirror(), parameter.getType().getTypeMirror() ) ) { + sourceArgument = parameter.getName(); + if ( assignment != null ) { + assignment.setSourceLocalVarName( + "(" + sourceType.createReferenceName() + ") " + sourceArgument ); + } + } + } + return new SubclassMapping( sourceType, sourceArgument, targetType, assignment ); + } + + private boolean isAbstractReturnTypeAllowed() { + return !method.getOptions().getSubclassMappings().isEmpty() + && ( method.getOptions().getBeanMapping().getSubclassExhaustiveStrategy().isAbstractReturnTypeAllowed() + || isCorrectlySealed() ); + } + + private boolean isCorrectlySealed() { + Type mappingSourceType = method.getMappingSourceType(); + return isCorrectlySealed( mappingSourceType ); + } + + private boolean isCorrectlySealed(Type mappingSourceType) { + if ( mappingSourceType.isSealed() ) { + List unusedPermittedSubclasses = + new ArrayList<>( mappingSourceType.getPermittedSubclasses() ); + method.getOptions().getSubclassMappings().forEach( subClassOption -> { + for ( Iterator iterator = unusedPermittedSubclasses.iterator(); + iterator.hasNext(); ) { + if ( ctx.getTypeUtils().isSameType( iterator.next(), subClassOption.getSource() ) ) { + iterator.remove(); + } + } + } ); + for ( Iterator iterator = unusedPermittedSubclasses.iterator(); + iterator.hasNext(); ) { + TypeMirror typeMirror = iterator.next(); + Type type = ctx.getTypeFactory().getType( typeMirror ); + if ( type.isAbstract() && isCorrectlySealed( type ) ) { + iterator.remove(); + } + } + return unusedPermittedSubclasses.isEmpty(); + } + return false; + } + + private void initializeMappingReferencesIfNeeded(Type resultTypeToMap) { + if ( mappingReferences == null && method instanceof SourceMethod ) { + Set readAndWriteTargetProperties = new HashSet<>( unprocessedTargetProperties.keySet() ); + readAndWriteTargetProperties.addAll( resultTypeToMap.getPropertyReadAccessors().keySet() ); + mappingReferences = forSourceMethod( + (SourceMethod) method, + resultTypeToMap, + readAndWriteTargetProperties, + ctx.getMessager(), + ctx.getTypeFactory() + ); + } + } + + /** + * @return builder is required when there is a returnTypeBuilder and the mapping method is not update method. + * However, builder is also required when there is a returnTypeBuilder, the mapping target is the builder and + * builder is not assignable to the return type (so without building). + */ + private boolean isBuilderRequired() { + if ( returnTypeBuilder == null ) { + return false; + } + if ( method.isUpdateMethod() ) { + // when @MappingTarget annotated parameter is the same type as the return type. + return !method.getResultType().isAssignableTo( method.getReturnType() ); + } + else { + // For non-update methods a builder is required when returnTypeBuilder is set + return true; + } + } + + private boolean shouldCallFinalizerMethod(Type returnTypeToConstruct ) { + if ( returnTypeToConstruct == null ) { + return false; + } + else if ( returnTypeToConstruct.isAssignableTo( method.getReturnType() ) ) { + // If the mapping type can be assigned to the return type then we + // don't need a finalizer method + return false; + } + + return returnTypeBuilder != null; + } + + private MethodReference getFinalizerMethod() { + return BuilderFinisherMethodResolver.getBuilderFinisherMethod( + method, + returnTypeBuilder, + ctx + ); + } + + /** + * If there were nested defined targets that have not been handled. Then we need to process them at the end. + */ + private void handleUnprocessedDefinedTargets() { + Iterator>> iterator = unprocessedDefinedTargets.entrySet().iterator(); + + // For each of the unprocessed defined targets forge a mapping for each of the + // method source parameters. The generated mappings are not going to use forged name based mappings. + while ( iterator.hasNext() ) { + Entry> entry = iterator.next(); + String propertyName = entry.getKey(); + if ( !unprocessedTargetProperties.containsKey( propertyName ) ) { + continue; + } + List sourceParameters = method.getSourceParameters(); + boolean forceUpdateMethod = sourceParameters.size() > 1; + for ( Parameter sourceParameter : sourceParameters ) { + SourceReference reference = new SourceReference.BuilderFromProperty() + .sourceParameter( sourceParameter ) + .name( propertyName ) + .build(); + + ReadAccessor targetPropertyReadAccessor = + method.getResultType().getReadAccessor( propertyName, forceUpdateMethod ); + MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); + PropertyMapping propertyMapping = new PropertyMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .target( + propertyName, + targetPropertyReadAccessor, + unprocessedTargetProperties.get( propertyName ) + ) + .sourceReference( reference ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mappingRefs.collectNestedDependsOn() ) + .forgeMethodWithMappingReferences( mappingRefs ) + .forceUpdateMethod( forceUpdateMethod ) + .forgedNamedBased( false ) + .build(); + + if ( propertyMapping != null ) { + unprocessedTargetProperties.remove( propertyName ); + unprocessedConstructorProperties.remove( propertyName ); + unprocessedSourceProperties.remove( propertyName ); + iterator.remove(); + propertyMappings.add( propertyMapping ); + // If we found a mapping for the unprocessed property then stop + break; + } + } + } + } + + private void handleUnmappedConstructorProperties() { + for ( Entry entry : unprocessedConstructorProperties.entrySet() ) { + Accessor accessor = entry.getValue(); + Type accessedType = ctx.getTypeFactory() + .getType( accessor.getAccessedType() ); + String targetPropertyName = entry.getKey(); + + propertyMappings.add( new JavaExpressionMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .javaExpression( accessedType.getNull() ) + .existingVariableNames( existingVariableNames ) + .target( targetPropertyName, null, accessor ) + .dependsOn( Collections.emptySet() ) + .mirror( null ) + .build() + ); + } + + unprocessedConstructorProperties.clear(); + } + /** * Sources the given mappings as per the dependency relationships given via {@code dependsOn()}. If a cycle is * detected, an error is reported. @@ -196,7 +810,7 @@ private void sortPropertyMappingsByDependencies() { final GraphAnalyzer graphAnalyzer = graphAnalyzerBuilder.build(); if ( !graphAnalyzer.getCycles().isEmpty() ) { - Set cycles = new HashSet(); + Set cycles = new HashSet<>(); for ( List cycle : graphAnalyzer.getCycles() ) { cycles.add( Strings.join( cycle, " -> " ) ); } @@ -207,24 +821,438 @@ private void sortPropertyMappingsByDependencies() { ); } else { - Collections.sort( - propertyMappings, new Comparator() { + propertyMappings.sort( Comparator.comparingInt( propertyMapping -> + graphAnalyzer.getTraversalSequence( propertyMapping.getName() ) ) ); + } + } - @Override - public int compare(PropertyMapping o1, PropertyMapping o2) { - if ( graphAnalyzer.getAllDescendants( o1.getName() ).contains( o2.getName() ) ) { - return 1; - } - else if ( graphAnalyzer.getAllDescendants( o2.getName() ).contains( o1.getName() ) ) { - return -1; - } - else { - return 0; - } + private boolean canResultTypeFromBeanMappingBeConstructed(Type resultType) { + + boolean error = true; + if ( resultType.isAbstract() ) { + ctx.getMessager().printMessage( + method.getExecutable(), + method.getOptions().getBeanMapping().getMirror(), + BEANMAPPING_ABSTRACT, + resultType.describe(), + method.getResultType().describe() + ); + error = false; + } + else if ( !resultType.isAssignableTo( method.getResultType() ) ) { + ctx.getMessager().printMessage( + method.getExecutable(), + method.getOptions().getBeanMapping().getMirror(), + BEANMAPPING_NOT_ASSIGNABLE, + resultType.describe(), + method.getResultType().describe() + ); + error = false; + } + else if ( !resultType.hasAccessibleConstructor() ) { + ctx.getMessager().printMessage( + method.getExecutable(), + method.getOptions().getBeanMapping().getMirror(), + Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, + resultType.describe() + ); + error = false; + } + return error; + } + + private boolean canReturnTypeBeConstructed(Type returnType) { + boolean error = true; + if ( returnType.isAbstract() ) { + ctx.getMessager().printMessage( + method.getExecutable(), + GENERAL_ABSTRACT_RETURN_TYPE, + returnType.describe() + ); + error = false; + } + else if ( !returnType.hasAccessibleConstructor() ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, + returnType.describe() + ); + error = false; + } + return error; + } + + private boolean isReturnTypeAbstractOrCanBeConstructed(Type returnType) { + boolean error = true; + if ( !returnType.isAbstract() && !returnType.hasAccessibleConstructor() ) { + ctx + .getMessager() + .printMessage( + method.getExecutable(), + Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, + returnType.describe() ); + error = false; + } + return error; + } + + /** + * Find a factory method for a return type or for a builder. + * @param returnTypeImpl the return type implementation to construct + * @param @selectionParameters + */ + private void initializeFactoryMethod(Type returnTypeImpl, SelectionParameters selectionParameters) { + List> matchingFactoryMethods = + ObjectFactoryMethodResolver.getMatchingFactoryMethods( + method, + returnTypeImpl, + selectionParameters, + ctx + ); + + if ( matchingFactoryMethods.isEmpty() ) { + if ( factoryMethod == null && returnTypeBuilder != null ) { + factoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( method, returnTypeBuilder ); + hasFactoryMethod = factoryMethod != null; + } + } + else if ( matchingFactoryMethods.size() == 1 ) { + factoryMethod = ObjectFactoryMethodResolver.getFactoryMethodReference( + method, + first( matchingFactoryMethods ), + ctx + ); + hasFactoryMethod = true; + } + else { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.GENERAL_AMBIGUOUS_FACTORY_METHOD, + returnTypeImpl.describe(), + matchingFactoryMethods.stream() + .map( SelectedMethod::getMethod ) + .map( Method::describe ) + .collect( Collectors.joining( ", " ) ) + ); + hasFactoryMethod = true; + } + } + + private ConstructorAccessor getConstructorAccessor(Type type) { + if ( type.isAbstract() ) { + // We cannot construct abstract classes. + // Usually we won't reach here, + // but if SubclassMapping is used with SubclassExhaustiveStrategy#RUNTIME_EXCEPTION + // then we will still generate the code. + // We shouldn't generate anything for those abstract types + return null; + } + + if ( type.isRecord() ) { + + List constructors = ElementFilter.constructorsIn( type.getTypeElement() + .getEnclosedElements() ); + + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + continue; + } + + // prefer constructor annotated with @Default + if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) { + return getConstructorAccessor( type, constructor ); + } + } + + + // Other than that, just get the record components and use them + List recordComponents = type.getRecordComponents(); + List parameterBindings = new ArrayList<>( recordComponents.size() ); + Map constructorAccessors = new LinkedHashMap<>(); + for ( Element recordComponent : recordComponents ) { + TypeMirror recordComponentMirror = ctx.getTypeUtils() + .asMemberOf( (DeclaredType) type.getTypeMirror(), recordComponent ); + String parameterName = recordComponent.getSimpleName().toString(); + Accessor accessor = createConstructorAccessor( + recordComponent, + recordComponentMirror, + parameterName + ); + constructorAccessors.put( + parameterName, + accessor + ); + + parameterBindings.add( ParameterBinding.fromTypeAndName( + ctx.getTypeFactory().getType( recordComponentMirror ), + accessor.getSimpleName() + ) ); + } + return new ConstructorAccessor( parameterBindings, constructorAccessors ); + } + + KotlinMetadata kotlinMetadata = type.getKotlinMetadata(); + if ( kotlinMetadata != null && kotlinMetadata.isDataClass() ) { + List constructors = ElementFilter.constructorsIn( type.getTypeElement() + .getEnclosedElements() ); + + Iterator constructorIterator = constructors.iterator(); + while ( constructorIterator.hasNext() ) { + ExecutableElement constructor = constructorIterator.next(); + if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + constructorIterator.remove(); + continue; + } + + // prefer constructor annotated with @Default + if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) { + return getConstructorAccessor( type, constructor ); + } + } + + ExecutableElement primaryConstructor = kotlinMetadata.determinePrimaryConstructor( constructors ); + + return primaryConstructor != null ? getConstructorAccessor( type, primaryConstructor ) : null; + } + + List constructors = ElementFilter.constructorsIn( type.getTypeElement() + .getEnclosedElements() ); + + // The rules for picking a constructor are the following: + // 1. Constructor annotated with @Default (from any package) has highest precedence + // 2. If there is a single public constructor then it would be used to construct the object + // 3. If a parameterless constructor exists then it would be used to construct the object, and the other + // constructors will be ignored + ExecutableElement defaultAnnotatedConstructor = null; + ExecutableElement parameterLessConstructor = null; + List accessibleConstructors = new ArrayList<>( constructors.size() ); + List publicConstructors = new ArrayList<>( ); + + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + continue; + } + + if ( hasDefaultAnnotationFromAnyPackage( constructor ) ) { + // We found a constructor annotated with @Default everything else is irrelevant + defaultAnnotatedConstructor = constructor; + break; + } + + if ( constructor.getParameters().isEmpty() ) { + parameterLessConstructor = constructor; + } + else { + accessibleConstructors.add( constructor ); + } + + if ( constructor.getModifiers().contains( Modifier.PUBLIC ) ) { + publicConstructors.add( constructor ); + } + } + + if ( defaultAnnotatedConstructor != null ) { + // If a default annotated constructor exists it will be used, it has highest precedence + return getConstructorAccessor( type, defaultAnnotatedConstructor ); + } + + if ( publicConstructors.size() == 1 ) { + // If there is a single public constructor then use that one + ExecutableElement publicConstructor = publicConstructors.get( 0 ); + if ( publicConstructor.getParameters().isEmpty() ) { + // The public parameterless constructor + return null; + } + + return getConstructorAccessor( type, publicConstructor ); + } + + if ( parameterLessConstructor != null ) { + // If there is a constructor without parameters use it + return null; + } + + if ( accessibleConstructors.isEmpty() ) { + return null; + } + + if ( accessibleConstructors.size() > 1 ) { + + ctx.getMessager().printMessage( + method.getExecutable(), + GENERAL_AMBIGUOUS_CONSTRUCTORS, + type, + constructors.stream() + .map( ExecutableElement::getParameters ) + .map( ps -> ps.stream() + .map( VariableElement::asType ) + .map( String::valueOf ) + .collect( Collectors.joining( ", ", type.getName() + "(", ")" ) ) + ) + .collect( Collectors.joining( ", " ) ) + ); + return new ConstructorAccessor( true, Collections.emptyList(), Collections.emptyMap() ); + } + else { + return getConstructorAccessor( type, accessibleConstructors.get( 0 ) ); + } + + } + + private ConstructorAccessor getConstructorAccessor(Type type, ExecutableElement constructor) { + List constructorParameters = ctx.getTypeFactory() + .getParameters( (DeclaredType) type.getTypeMirror(), constructor ); + + List constructorProperties = null; + for ( AnnotationMirror annotationMirror : constructor.getAnnotationMirrors() ) { + if ( annotationMirror.getAnnotationType() + .asElement() + .getSimpleName() + .contentEquals( "ConstructorProperties" ) ) { + for ( Entry entry : annotationMirror + .getElementValues() + .entrySet() ) { + if ( entry.getKey().getSimpleName().contentEquals( "value" ) ) { + constructorProperties = getArrayValues( entry.getValue() ); + break; } } + break; + } + } + + if ( constructorProperties == null ) { + Map constructorAccessors = new LinkedHashMap<>(); + List parameterBindings = new ArrayList<>( constructorParameters.size() ); + for ( Parameter constructorParameter : constructorParameters ) { + String parameterName = constructorParameter.getName(); + Element parameterElement = constructorParameter.getElement(); + Accessor constructorAccessor = createConstructorAccessor( + parameterElement, + constructorParameter.getType().getTypeMirror(), + parameterName + ); + constructorAccessors.put( + parameterName, + constructorAccessor + ); + parameterBindings.add( ParameterBinding.fromTypeAndName( + constructorParameter.getType(), + constructorAccessor.getSimpleName() + ) ); + } + + return new ConstructorAccessor( parameterBindings, constructorAccessors ); + } + else if ( constructorProperties.size() != constructorParameters.size() ) { + ctx.getMessager().printMessage( + method.getExecutable(), + GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS, + type ); + return new ConstructorAccessor( true, Collections.emptyList(), Collections.emptyMap() ); } + else { + Map constructorAccessors = new LinkedHashMap<>(); + List parameterBindings = new ArrayList<>( constructorProperties.size() ); + for ( int i = 0; i < constructorProperties.size(); i++ ) { + String parameterName = constructorProperties.get( i ); + Parameter constructorParameter = constructorParameters.get( i ); + Element parameterElement = constructorParameter.getElement(); + Accessor constructorAccessor = createConstructorAccessor( + parameterElement, + constructorParameter.getType().getTypeMirror(), + parameterName + ); + constructorAccessors.put( + parameterName, + constructorAccessor + ); + parameterBindings.add( ParameterBinding.fromTypeAndName( + constructorParameter.getType(), + constructorAccessor.getSimpleName() + ) ); + } + + return new ConstructorAccessor( parameterBindings, constructorAccessors ); + } + } + + private Accessor createConstructorAccessor(Element element, TypeMirror accessedType, String parameterName) { + String safeParameterName = Strings.getSafeVariableName( + parameterName, + existingVariableNames + ); + existingVariableNames.add( safeParameterName ); + return new ElementAccessor( element, accessedType, safeParameterName ); + } + + private boolean hasDefaultAnnotationFromAnyPackage(Element element) { + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + if ( annotationMirror.getAnnotationType() + .asElement() + .getSimpleName() + .contentEquals( "Default" ) ) { + return true; + } + } + + return false; + } + + private List getArrayValues(AnnotationValue av) { + + if ( av.getValue() instanceof List ) { + List result = new ArrayList<>(); + for ( AnnotationValue v : getValueAsList( av ) ) { + Object value = v.getValue(); + if ( value instanceof String ) { + result.add( (String) value ); + } + else { + return null; + } + } + return result; + } + else { + return null; + } + } + + @SuppressWarnings("unchecked") + private List getValueAsList(AnnotationValue av) { + return (List) av.getValue(); + } + + /** + * Determine whether defined mappings should be handled on the result type. + * They should be, if any of the following is true: + *
      + *
    • The {@code resultTypeToMap} is not abstract
    • + *
    • There is a factory method
    • + *
    • The method is an update method
    • + *
    + * Otherwise, it means that we have reached this because subclass mappings are being used + * and the chosen strategy is runtime exception. + * + * @param resultTypeToMap the type in which the defined target properties are defined + * @return {@code true} if defined mappings should be handled for the result type, {@code false} otherwise + */ + private boolean shouldHandledDefinedMappings(Type resultTypeToMap) { + if ( !resultTypeToMap.isAbstract() ) { + return true; + } + + if ( hasFactoryMethod ) { + return true; + } + + if ( method.isUpdateMethod() ) { + return true; + } + + return false; } /** @@ -236,135 +1264,441 @@ else if ( graphAnalyzer.getAllDescendants( o2.getName() ).contains( o1.getName() *

    * It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues * in search of more problems. + * + * @param resultTypeToMap the type in which the defined target properties are defined */ - private boolean handleDefinedSourceMappings() { + private boolean handleDefinedMappings(Type resultTypeToMap) { + boolean errorOccurred = false; + Set handledTargets = new HashSet<>(); - Set handledTargets = new HashSet(); + // first we have to handle nested target mappings + if ( mappingReferences.hasNestedTargetReferences() ) { + errorOccurred = handleDefinedNestedTargetMapping( handledTargets, resultTypeToMap ); + } - for ( Map.Entry> entry : method.getMappingOptions().getMappings().entrySet() ) { - for ( Mapping mapping : entry.getValue() ) { + for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { + if ( mapping.isValid() ) { + String target = mapping.getTargetReference().getShallowestPropertyName(); + if ( target == null ) { + // When the shallowest property name is null then it is for @Mapping(target = ".") + if ( this.targetThisReferences == null ) { + this.targetThisReferences = new ArrayList<>(); + } + this.targetThisReferences.add( mapping ); + continue; + } + if ( !handledTargets.contains( target ) ) { + if ( handleDefinedMapping( mapping, resultTypeToMap, handledTargets ) ) { + errorOccurred = true; + } + } + if ( mapping.getSourceReference() != null ) { + String source = mapping.getSourceReference().getShallowestPropertyName(); + if ( source != null ) { + unprocessedSourceProperties.remove( source ); + } + } + } + else { + errorOccurred = true; + } + } + + // remove the remaining name based properties + for ( String handledTarget : handledTargets ) { + unprocessedTargetProperties.remove( handledTarget ); + unprocessedConstructorProperties.remove( handledTarget ); + unprocessedDefinedTargets.remove( handledTarget ); + } - PropertyMapping propertyMapping = null; + return errorOccurred; + } - // fetch the target property - ExecutableElement targetWriteAccessor = unprocessedTargetProperties.get( mapping.getTargetName() ); - if ( targetWriteAccessor == null ) { - ctx.getMessager().printMessage( - method.getExecutable(), + private boolean handleDefinedNestedTargetMapping(Set handledTargets, Type resultTypeToMap) { + + NestedTargetPropertyMappingHolder holder = new NestedTargetPropertyMappingHolder.Builder() + .mappingContext( ctx ) + .method( method ) + .targetPropertiesWriteAccessors( unprocessedTargetProperties ) + .targetPropertyType( resultTypeToMap ) + .mappingReferences( mappingReferences ) + .existingVariableNames( existingVariableNames ) + .build(); + + unprocessedSourceParameters.removeAll( holder.getProcessedSourceParameters() ); + propertyMappings.addAll( holder.getPropertyMappings() ); + handledTargets.addAll( holder.getHandledTargets() ); + // Store all the unprocessed defined targets. + for ( Entry> entry : holder.getUnprocessedDefinedTarget() + .entrySet() ) { + if ( entry.getValue().isEmpty() ) { + continue; + } + unprocessedDefinedTargets.put( entry.getKey(), entry.getValue() ); + } + return holder.hasErrorOccurred(); + } + + private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTypeToMap, + Set handledTargets) { + boolean errorOccured = false; + + PropertyMapping propertyMapping = null; + + TargetReference targetRef = mappingRef.getTargetReference(); + MappingOptions mapping = mappingRef.getMapping(); + + // unknown properties given via dependsOn()? + for ( String dependency : mapping.getDependsOn() ) { + if ( !targetProperties.contains( dependency ) ) { + ctx.getMessager().printMessage( + method.getExecutable(), + mapping.getMirror(), + mapping.getDependsOnAnnotationValue(), + Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON, + dependency + ); + errorOccured = true; + } + } + + String targetPropertyName = first( targetRef.getPropertyEntries() ); + + // check if source / expression / constant are not somehow handled already + if ( unprocessedDefinedTargets.containsKey( targetPropertyName ) ) { + return false; + } + + Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName ); + ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor( + targetPropertyName, + method.getSourceParameters().size() == 1 + ); + + if ( targetWriteAccessor == null ) { + if ( targetReadAccessor == null ) { + MappingOptions.InheritContext inheritContext = mapping.getInheritContext(); + if ( inheritContext != null ) { + if ( inheritContext.isForwarded() && + inheritContext.getTemplateMethod().isUpdateMethod() != method.isUpdateMethod() ) { + // When a configuration is inherited and the template method is not same type as the current + // method then we can safely ignore this mapping. + // This means that a property which is inherited might be present for a direct mapping + // via the Builder, but not for an update mapping (directly on the object itself), + // or vice versa + return false; + } + else if ( inheritContext.isReversed() ) { + // When a configuration is reverse inherited and there are no read or write accessor + // then we should ignore this mapping. + // This most likely means that we were mapping the source parameter to the target. + // If the error is due to something else it will be reported on the original mapping + return false; + } + } + + Message msg; + String[] args; + + Set readAccessors = resultTypeToMap.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + + Element elementForMessage = mapping.getElement(); + if ( elementForMessage == null ) { + elementForMessage = method.getExecutable(); + } + + if ( mapping.isIgnored() && mapping.getElement() == null ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mostSimilarProperty + }; + } + else { + if ( targetRef.getPathProperties().isEmpty() ) { + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mostSimilarProperty + }; + } + else { + List pathProperties = new ArrayList<>( targetRef.getPathProperties() ); + pathProperties.add( mostSimilarProperty ); + msg = Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE; + args = new String[] { + targetPropertyName, + resultTypeToMap.describe(), + mapping.getTargetName(), + Strings.join( pathProperties, "." ) + }; + } + } + + ctx.getMessager() + .printMessage( + elementForMessage, mapping.getMirror(), - mapping.getSourceAnnotationValue(), - Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RETURNTYPE, + mapping.getTargetAnnotationValue(), + msg, + args + ); + return true; + } + else if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { + // read only reversed mappings are implicitly ignored + return false; + } + else if ( !mapping.isIgnored() ) { + // report an error for read only mappings + Message msg; + Object[] args; + + if ( Objects.equals( targetPropertyName, mapping.getTargetName() ) ) { + msg = Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE; + args = new Object[] { + mapping.getTargetName(), + resultTypeToMap.describe() + }; + } + else { + msg = Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_TYPE; + args = new Object[] { + targetPropertyName, + resultTypeToMap.describe(), mapping.getTargetName() - ); - errorOccurred = true; + }; } + ctx.getMessager() + .printMessage( + mapping.getElement(), + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + msg, + args + ); + return true; + } + } + + // check the mapping options + // its an ignored property mapping + if ( mapping.isIgnored() ) { + if ( targetWriteAccessor != null && targetWriteAccessor.getAccessorType() == AccessorType.PARAMETER ) { + // Even though the property is ignored this is a constructor parameter. + // Therefore we have to initialize it + Type accessedType = ctx.getTypeFactory() + .getType( targetWriteAccessor.getAccessedType() ); + + propertyMapping = new JavaExpressionMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .javaExpression( accessedType.getNull() ) + .existingVariableNames( existingVariableNames ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) + .dependsOn( mapping.getDependsOn() ) + .mirror( mapping.getMirror() ) + .build(); + } + handledTargets.add( targetPropertyName ); + } - // unknown properties given via dependsOn()? - for ( String dependency : mapping.getDependsOn() ) { - if ( !targetProperties.contains( dependency ) ) { - ctx.getMessager().printMessage( - method.getExecutable(), - mapping.getMirror(), - mapping.getDependsOnAnnotationValue(), - Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON, - dependency - ); - errorOccurred = true; - } - } + // it's a constant + // if we have an unprocessed target that means that it most probably is nested and we should + // not generated any mapping for it now. Eventually it will be done though + else if ( mapping.getConstant() != null ) { + + propertyMapping = new ConstantMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .constantExpression( mapping.getConstant() ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) + .formattingParameters( mapping.getFormattingParameters() ) + .selectionParameters( mapping.getSelectionParameters() ) + .options( mapping ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mapping.getDependsOn() ) + .mirror( mapping.getMirror() ) + .build(); + handledTargets.add( targetPropertyName ); + } - // check the mapping options - // its an ignored property mapping - if ( mapping.isIgnored() ) { - propertyMapping = null; - handledTargets.add( mapping.getTargetName() ); - } - - // its a plain-old property mapping - else if ( mapping.getSourceName() != null ) { - - // determine source parameter - SourceReference sourceRef = mapping.getSourceReference(); - if ( sourceRef.isValid() ) { - - if ( targetWriteAccessor != null ) { - - // targetProperty == null can occur: we arrived here because we want as many errors - // as possible before we stop analysing - propertyMapping = new PropertyMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .targetWriteAccessor( targetWriteAccessor ) - .targetReadAccessor( getTargetPropertyReadAccessor( mapping.getTargetName() ) ) - .targetPropertyName( mapping.getTargetName() ) - .sourceReference( sourceRef ) - .qualifiers( mapping.getQualifiers() ) - .resultType( mapping.getResultType() ) - .dateFormat( mapping.getDateFormat() ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping.getDependsOn() ) - .defaultValue( mapping.getDefaultValue() ) - .build(); - handledTargets.add( mapping.getTargetName() ); - unprocessedSourceParameters.remove( sourceRef.getParameter() ); + // it's an expression + // if we have an unprocessed target that means that it most probably is nested and we should + // not generated any mapping for it now. Eventually it will be done though + else if ( mapping.getJavaExpression() != null ) { + + propertyMapping = new JavaExpressionMappingBuilder() + .mappingContext( ctx ) + .sourceMethod( method ) + .javaExpression( mapping.getJavaExpression() ) + .existingVariableNames( existingVariableNames ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) + .dependsOn( mapping.getDependsOn() ) + .mirror( mapping.getMirror() ) + .build(); + handledTargets.add( targetPropertyName ); + } + // it's a plain-old property mapping + else { + + SourceReference sourceRef = mappingRef.getSourceReference(); + // sourceRef is not defined, check if a source property has the same name + if ( sourceRef == null ) { + // Here we follow the same rules as when we implicitly map + // When we implicitly map we first do property name based mapping + // i.e. look for matching properties in the source types + // and then do parameter name based mapping + for ( Parameter sourceParameter : method.getSourceParameters() ) { + SourceReference matchingSourceRef = getSourceRefByTargetName( + sourceParameter, + targetPropertyName + ); + if ( matchingSourceRef != null ) { + if ( sourceRef != null ) { + errorOccured = true; + // This can only happen when the target property matches multiple properties + // within the different source parameters + ctx.getMessager() + .printMessage( + method.getExecutable(), + mappingRef.getMapping().getMirror(), + Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, + targetPropertyName + ); + break; } - } - else { - errorOccurred = true; + // We can't break here since it is possible that the same property exists in multiple + // source parameters + sourceRef = matchingSourceRef; } } - // its a constant - else if ( mapping.getConstant() != null && targetWriteAccessor != null ) { + } - propertyMapping = new ConstantMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .constantExpression( "\"" + mapping.getConstant() + "\"" ) - .targetWriteAccessor( targetWriteAccessor ) - .targetReadAccessor( getTargetPropertyReadAccessor( mapping.getTargetName() ) ) - .targetPropertyName( mapping.getTargetName() ) - .dateFormat( mapping.getDateFormat() ) - .qualifiers( mapping.getQualifiers() ) - .resultType( mapping.getResultType() ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping.getDependsOn() ) - .build(); - handledTargets.add( mapping.getTargetName() ); - } + if ( sourceRef == null ) { + // still no match. Try if one of the parameters has the same name + sourceRef = method.getSourceParameters() + .stream() + .filter( p -> targetPropertyName.equals( p.getName() ) ) + .findAny() + .map( p -> new SourceReference.BuilderFromProperty() + .sourceParameter( p ) + .name( targetPropertyName ) + .build() ) + .orElse( null ); + } - // its an expression - else if ( mapping.getJavaExpression() != null && targetWriteAccessor != null ) { + if ( sourceRef != null ) { + // sourceRef == null is not considered an error here + if ( sourceRef.isValid() ) { + Parameter sourceParameter = sourceRef.getParameter(); - propertyMapping = new JavaExpressionMappingBuilder() + // targetProperty == null can occur: we arrived here because we want as many errors + // as possible before we stop analysing + propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) - .javaExpression( mapping.getJavaExpression() ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) + .sourcePropertyName( mapping.getSourceName() ) + .sourceReference( sourceRef.withParameter( + sourceParametersReassignments.get( sourceParameter.getName() ) ) ) + .selectionParameters( mapping.getSelectionParameters() ) + .formattingParameters( mapping.getFormattingParameters() ) .existingVariableNames( existingVariableNames ) - .targetWriteAccessor( targetWriteAccessor ) - .targetReadAccessor( targetWriteAccessor ) - .targetPropertyName( mapping.getTargetName() ) .dependsOn( mapping.getDependsOn() ) + .defaultValue( mapping.getDefaultValue() ) + .defaultJavaExpression( mapping.getDefaultJavaExpression() ) + .conditionJavaExpression( mapping.getConditionJavaExpression() ) + .mirror( mapping.getMirror() ) + .options( mapping ) .build(); - handledTargets.add( mapping.getTargetName() ); + handledTargets.add( targetPropertyName ); + unprocessedSourceParameters.remove( sourceParameter ); + // If the source parameter was directly mapped + if ( sourceRef.getPropertyEntries().isEmpty() ) { + // Ignore all of its source properties completely + ignoreSourceProperties( sourceParameter ); + } + else { + unprocessedSourceProperties.remove( sourceRef.getShallowestPropertyName() ); + } } + else { + errorOccured = true; + } + } + else { + errorOccured = true; - // remaining are the mappings without a 'source' so, 'only' a date format or qualifiers - - if ( propertyMapping != null ) { - propertyMappings.add( propertyMapping ); + if ( method.getSourceParameters().size() == 1 ) { + ctx.getMessager() + .printMessage( + method.getExecutable(), + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PROPERTY_FROM_TARGET, + method.getSourceParameters().get( 0 ).getName(), + targetPropertyName + ); + } + else { + ctx.getMessager() + .printMessage( + method.getExecutable(), + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET, + targetPropertyName + ); } } } - - for ( String handledTarget : handledTargets ) { - // In order to avoid: "Unknown property foo in return type" in case of duplicate - // target mappings - unprocessedTargetProperties.remove( handledTarget ); + // remaining are the mappings without a 'source' so, 'only' a date format or qualifiers + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); } - return errorOccurred; + return errorOccured; + } + + /** + * When target this mapping present, iterates over unprocessed targets. + *

    + * When a target property matches its name with the (nested) source property, it is added to the list if and + * only if it is an unprocessed target property. + *

    + * duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)} + */ + private void applyTargetThisMapping() { + if ( this.targetThisReferences == null ) { + return; + } + Set handledTargetProperties = new HashSet<>(); + for ( MappingReference targetThis : this.targetThisReferences ) { + + // handle all prior unprocessed target properties, but let duplicates fall through + List sourceRefs = targetThis + .getSourceReference() + .push( ctx.getTypeFactory(), ctx.getMessager(), method ) + .stream() + .filter( sr -> unprocessedTargetProperties.containsKey( sr.getDeepestPropertyName() ) + || handledTargetProperties.contains( sr.getDeepestPropertyName() ) ) + .collect( Collectors.toList() ); + + // apply name based mapping + applyPropertyNameBasedMapping( sourceRefs ); + + // add handled target properties + handledTargetProperties.addAll( sourceRefs.stream() + .map( SourceReference::getDeepestPropertyName ) + .collect( + Collectors.toList() ) ); + } } /** @@ -374,100 +1708,73 @@ else if ( mapping.getJavaExpression() != null && targetWriteAccessor != null ) { * the set of remaining target properties. */ private void applyPropertyNameBasedMapping() { - Iterator> targetProperties = - unprocessedTargetProperties.entrySet().iterator(); - - // usually there should be only one getter; only for Boolean there may be two: isFoo() and getFoo() - List candidates = new ArrayList( 2 ); - - while ( targetProperties.hasNext() ) { - Entry targetProperty = targetProperties.next(); - - PropertyMapping propertyMapping = null; - - if ( propertyMapping == null ) { - - for ( Parameter sourceParameter : method.getSourceParameters() ) { - - if ( sourceParameter.getType().isPrimitive() ) { - continue; - } - - Collection sourceReadAccessors = - sourceParameter.getType().getPropertyReadAccessors().values(); - for ( ExecutableElement sourceReadAccessor : sourceReadAccessors ) { - String sourcePropertyName = Executables.getPropertyName( sourceReadAccessor ); - - if ( sourcePropertyName.equals( targetProperty.getKey() ) ) { - candidates.add( sourceReadAccessor ); - } - } - - PropertyMapping newPropertyMapping = null; - ExecutableElement sourceAccessor = getSourceAccessor( targetProperty.getKey(), candidates ); - if ( sourceAccessor != null ) { - Mapping mapping = method.getSingleMappingByTargetPropertyName( targetProperty.getKey() ); - - TypeElement sourceType = sourceParameter.getType().getTypeElement(); - - SourceReference sourceRef = new SourceReference.BuilderFromProperty() - .sourceParameter( sourceParameter ) - .type( ctx.getTypeFactory().getReturnType( sourceType, sourceAccessor ) ) - .accessor( sourceAccessor ) - .name( targetProperty.getKey() ) - .build(); - - newPropertyMapping = new PropertyMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .targetWriteAccessor( targetProperty.getValue() ) - .targetReadAccessor( getTargetPropertyReadAccessor( targetProperty.getKey() ) ) - .targetPropertyName( targetProperty.getKey() ) - .sourceReference( sourceRef ) - .qualifiers( mapping != null ? mapping.getQualifiers() : null ) - .resultType( mapping != null ? mapping.getResultType() : null ) - .dateFormat( mapping != null ? mapping.getDateFormat() : null ) - .defaultValue( mapping != null ? mapping.getDefaultValue() : null ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) - .build(); - - unprocessedSourceParameters.remove( sourceParameter ); - - } - // candidates are handled - candidates.clear(); - - if ( propertyMapping != null && newPropertyMapping != null ) { - // TODO improve error message - ctx.getMessager().printMessage( - method.getExecutable(), - Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, - targetProperty.getKey() - ); - break; - } - else if ( newPropertyMapping != null ) { - propertyMapping = newPropertyMapping; - } + List sourceReferences = new ArrayList<>(); + for ( String targetPropertyName : unprocessedTargetProperties.keySet() ) { + for ( Parameter sourceParameter : method.getSourceParameters() ) { + SourceReference sourceRef = getSourceRefByTargetName( sourceParameter, targetPropertyName ); + if ( sourceRef != null ) { + sourceReferences.add( sourceRef ); } } + } + applyPropertyNameBasedMapping( sourceReferences ); + } + + /** + * Iterates over all target properties and all source parameters. + *

    + * When a property name match occurs, the remainder will be checked for duplicates. Matches will be removed from + * the set of remaining target properties. + */ + private void applyPropertyNameBasedMapping(List sourceReferences) { + + for ( SourceReference sourceRef : sourceReferences ) { + + String targetPropertyName = sourceRef.getDeepestPropertyName(); + Accessor targetPropertyWriteAccessor = unprocessedTargetProperties.remove( targetPropertyName ); + unprocessedConstructorProperties.remove( targetPropertyName ); + if ( targetPropertyWriteAccessor == null ) { + // TODO improve error message + ctx.getMessager() + .printMessage( method.getExecutable(), + Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, + targetPropertyName + ); + continue; + } + + ReadAccessor targetPropertyReadAccessor = + method.getResultType() + .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); + MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); + PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) + .sourceMethod( method ) + .sourcePropertyName( targetPropertyName ) + .target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor ) + .sourceReference( sourceRef ) + .existingVariableNames( existingVariableNames ) + .forgeMethodWithMappingReferences( mappingRefs ) + .options( method.getOptions().getBeanMapping() ) + .build(); + + unprocessedSourceParameters.remove( sourceRef.getParameter() ); if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); - targetProperties.remove(); } + unprocessedDefinedTargets.remove( targetPropertyName ); + unprocessedSourceProperties.remove( targetPropertyName ); } } private void applyParameterNameBasedMapping() { - Iterator> targetProperties = + Iterator> targetPropertyEntriesIterator = unprocessedTargetProperties.entrySet().iterator(); - while ( targetProperties.hasNext() ) { + while ( targetPropertyEntriesIterator.hasNext() ) { - Entry targetProperty = targetProperties.next(); + Entry targetProperty = targetPropertyEntriesIterator.next(); Iterator sourceParameters = unprocessedSourceParameters.iterator(); @@ -475,166 +1782,509 @@ private void applyParameterNameBasedMapping() { Parameter sourceParameter = sourceParameters.next(); if ( sourceParameter.getName().equals( targetProperty.getKey() ) ) { - Mapping mapping = method.getSingleMappingByTargetPropertyName( targetProperty.getKey() ); SourceReference sourceRef = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) .name( targetProperty.getKey() ) .build(); + ReadAccessor targetPropertyReadAccessor = + method.getResultType() + .getReadAccessor( targetProperty.getKey(), method.getSourceParameters().size() == 1 ); + MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) - .targetWriteAccessor( targetProperty.getValue() ) - .targetReadAccessor( getTargetPropertyReadAccessor( targetProperty.getKey() ) ) - .targetPropertyName( targetProperty.getKey() ) + .target( targetProperty.getKey(), targetPropertyReadAccessor, targetProperty.getValue() ) .sourceReference( sourceRef ) - .qualifiers( mapping != null ? mapping.getQualifiers() : null ) - .resultType( mapping != null ? mapping.getResultType() : null ) - .dateFormat( mapping != null ? mapping.getDateFormat() : null ) .existingVariableNames( existingVariableNames ) - .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) + .forgeMethodWithMappingReferences( mappingRefs ) + .options( method.getOptions().getBeanMapping() ) .build(); propertyMappings.add( propertyMapping ); - targetProperties.remove(); + targetPropertyEntriesIterator.remove(); sourceParameters.remove(); + unprocessedDefinedTargets.remove( targetProperty.getKey() ); + unprocessedSourceProperties.remove( targetProperty.getKey() ); + unprocessedConstructorProperties.remove( targetProperty.getKey() ); + ignoreSourceProperties( sourceParameter ); } } } } - private ExecutableElement getSourceAccessor(String sourcePropertyName, List candidates) { - if ( candidates.isEmpty() ) { - return null; + private void ignoreSourceProperties(Parameter sourceParameter) { + // The source parameter was directly mapped so ignore all of its source properties completely + if ( !sourceParameter.getType().isPrimitive() && !sourceParameter.getType().isArrayType() ) { + // We explicitly ignore source properties from primitives or array types + Map readAccessors = sourceParameter.getType() + .getPropertyReadAccessors(); + for ( String sourceProperty : readAccessors.keySet() ) { + unprocessedSourceProperties.remove( sourceProperty ); + } } - else if ( candidates.size() == 1 ) { - return candidates.get( 0 ); + } + + private SourceReference getSourceRefByTargetName(Parameter sourceParameter, String targetPropertyName) { + + SourceReference sourceRef = null; + + Type sourceParameterType = sourceParameter.getType(); + Parameter sourceParameterToUse = sourceParameter; + if ( sourceParameterType.isOptionalType() ) { + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParameterToUse = sourceParametersReassignments.get( sourceParameter.getName() ); } - // can only be the case for Booleans: isFoo() and getFoo(); The latter is preferred then - else if ( candidates.size() == 2 ) { - if ( candidates.get( 0 ).getSimpleName().toString().startsWith( "get" ) ) { - return candidates.get( 0 ); - } - else { - return candidates.get( 1 ); - } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() ) { + return sourceRef; } - // Should never really happen - else { - ctx.getMessager().printMessage( - method.getExecutable(), - Message.BEANMAPPING_SEVERAL_POSSIBLE_TARGET_ACCESSORS, - sourcePropertyName - ); - return null; + ReadAccessor sourceReadAccessor = sourceParameterType + .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); + if ( sourceReadAccessor != null ) { + // property mapping + PresenceCheckAccessor sourcePresenceChecker = + sourceParameterType.getPresenceChecker( targetPropertyName ); + + DeclaredType declaredSourceType = (DeclaredType) sourceParameterType.getTypeMirror(); + Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); + sourceRef = new SourceReference.BuilderFromProperty().sourceParameter( sourceParameterToUse ) + .type( returnType ) + .readAccessor( sourceReadAccessor ) + .presenceChecker( sourcePresenceChecker ) + .name( targetPropertyName ) + .build(); } + return sourceRef; } - private ExecutableElement getTargetPropertyReadAccessor( String propertyName ) { - return method.getResultType().getPropertyReadAccessors().get( propertyName ); + private MappingReferences extractMappingReferences(String targetProperty, boolean restrictToDefinedMappings) { + if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) { + Set mappings = unprocessedDefinedTargets.get( targetProperty ); + return new MappingReferences( mappings, restrictToDefinedMappings ); + } + return null; } - /** - * Returns the effective policy for reporting unmapped getReturnType properties. If explicitly set via - * {@code Mapper}, this value will be returned. Otherwise the value from the corresponding processor option will - * be returned. If that is not set either, the default value from {@code Mapper#unmappedTargetPolicy()} will be - * returned. - * - * @return The effective policy for reporting unmapped target properties. - */ - private ReportingPolicy getEffectiveUnmappedTargetPolicy() { - MapperConfiguration mapperSettings = MapperConfiguration.getInstanceOn( ctx.getMapperTypeElement() ); - boolean setViaAnnotation = mapperSettings.isSetUnmappedTargetPolicy(); - ReportingPolicy annotationValue = ReportingPolicy.valueOf( mapperSettings.unmappedTargetPolicy() ); - - if ( setViaAnnotation - || ctx.getOptions().getUnmappedTargetPolicy() == null ) { - return annotationValue; + private ReportingPolicyGem getUnmappedTargetPolicy() { + if ( mappingReferences.isForForgedMethods() ) { + return ReportingPolicyGem.IGNORE; } - else { - return ctx.getOptions().getUnmappedTargetPolicy(); + // If we have ignoreByDefault = true, unprocessed target properties are not an issue. + if ( method.getOptions().getBeanMapping().isIgnoredByDefault() ) { + return ReportingPolicyGem.IGNORE; + } + if ( method.getOptions().getBeanMapping() != null ) { + return method.getOptions().getBeanMapping().unmappedTargetPolicy(); } + return method.getOptions().getMapper().unmappedTargetPolicy(); } - private void reportErrorForUnmappedTargetPropertiesIfRequired() { + private void reportErrorForUnmappedTargetPropertiesIfRequired(Type resultType, + boolean constructorAccessorHadError) { // fetch settings from element to implement - ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy(); + ReportingPolicyGem unmappedTargetPolicy = getUnmappedTargetPolicy(); + + if ( targetProperties.isEmpty() ) { + if ( method instanceof ForgedMethod ) { + ForgedMethod forgedMethod = (ForgedMethod) method; + if ( forgedMethod.getHistory() == null ) { + Type sourceType = this.method.getParameters().get( 0 ).getType(); + Type targetType = this.method.getReturnType(); + ctx.getMessager().printMessage( + this.method.getExecutable(), + Message.PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND, + sourceType.describe(), + targetType.describe(), + targetType.describe(), + sourceType.describe() + ); + } + else { + ForgedMethodHistory history = forgedMethod.getHistory(); + ctx.getMessager().printMessage( + this.method.getExecutable(), + Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, + history.createSourcePropertyErrorMessage(), + history.getTargetType().describe(), + history.createTargetPropertyName(), + history.getTargetType().describe(), + history.getSourceType().describe() + ); + } + } + else if ( !constructorAccessorHadError ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES, + resultType.describe() + ); + } + } + else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { + + Message unmappedPropertiesMsg; + Message unmappedForgedPropertiesMsg; + if ( unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ) { + unmappedPropertiesMsg = Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR; + unmappedForgedPropertiesMsg = Message.BEANMAPPING_UNMAPPED_FORGED_TARGETS_ERROR; + } + else { + unmappedPropertiesMsg = Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING; + unmappedForgedPropertiesMsg = Message.BEANMAPPING_UNMAPPED_FORGED_TARGETS_WARNING; + } + + reportErrorForUnmappedProperties( + unprocessedTargetProperties, + unmappedPropertiesMsg, + unmappedForgedPropertiesMsg + ); + + } + } + + private ReportingPolicyGem getUnmappedSourcePolicy() { + if ( mappingReferences.isForForgedMethods() ) { + return ReportingPolicyGem.IGNORE; + } + return method.getOptions().getBeanMapping().unmappedSourcePolicy(); + } + + private void reportErrorForUnmappedSourcePropertiesIfRequired() { + ReportingPolicyGem unmappedSourcePolicy = getUnmappedSourcePolicy(); + if ( !unprocessedSourceProperties.isEmpty() && unmappedSourcePolicy.requiresReport() ) { + Message unmappedPropertiesMsg; + Message unmappedForgedPropertiesMsg; + if ( unmappedSourcePolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ) { + unmappedPropertiesMsg = Message.BEANMAPPING_UNMAPPED_SOURCES_ERROR; + unmappedForgedPropertiesMsg = Message.BEANMAPPING_UNMAPPED_FORGED_SOURCES_ERROR; + } + else { + unmappedPropertiesMsg = Message.BEANMAPPING_UNMAPPED_SOURCES_WARNING; + unmappedForgedPropertiesMsg = Message.BEANMAPPING_UNMAPPED_FORGED_SOURCES_WARNING; + } - if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { + reportErrorForUnmappedProperties( + unprocessedSourceProperties, + unmappedPropertiesMsg, + unmappedForgedPropertiesMsg ); + } + } - Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ? - Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING; + private void reportErrorForUnmappedProperties(Map unmappedProperties, + Message unmappedPropertiesMsg, + Message unmappedForgedPropertiesMsg) { + if ( !( method instanceof ForgedMethod ) ) { + Object[] args = new Object[] { + MessageFormat.format( + "{0,choice,1#property|1 typeParameters = parameterType.getTypeParameters(); + if ( typeParameters.size() != 2 || !typeParameters.get( 0 ).isString() ) { + Message message = typeParameters.isEmpty() ? + Message.MAPTOBEANMAPPING_RAW_MAP : + Message.MAPTOBEANMAPPING_WRONG_KEY_TYPE; + ctx.getMessager() + .printMessage( + method.getExecutable(), + message, + sourceParameter.getName(), + String.format( + "Map<%s,%s>", + !typeParameters.isEmpty() ? typeParameters.get( 0 ).describe() : "", + typeParameters.size() > 1 ? typeParameters.get( 1 ).describe() : "" + ) + ); + } + } + } + } + } + + private static class ConstructorAccessor { + private final boolean hasError; + private final List parameterBindings; + private final Map constructorAccessors; + + private ConstructorAccessor( + List parameterBindings, + Map constructorAccessors) { + this( false, parameterBindings, constructorAccessors ); + } + + private ConstructorAccessor(boolean hasError, List parameterBindings, + Map constructorAccessors) { + this.hasError = hasError; + this.parameterBindings = parameterBindings; + this.constructorAccessors = constructorAccessors; + } } - private BeanMappingMethod(SourceMethod method, + //CHECKSTYLE:OFF + private BeanMappingMethod(Method method, + List annotations, + Collection existingVariableNames, List propertyMappings, MethodReference factoryMethod, boolean mapNullToDefault, - Type resultType, - Collection existingVariableNames, + Type returnTypeToConstruct, + BuilderType returnTypeBuilder, List beforeMappingReferences, - List afterMappingReferences) { - super( method, existingVariableNames, beforeMappingReferences, afterMappingReferences ); + List afterMappingReferences, + List beforeMappingReferencesWithFinalizedReturnType, + List afterMappingReferencesWithFinalizedReturnType, + List afterMappingReferencesWithOptionalReturnType, + MethodReference finalizerMethod, + MappingReferences mappingReferences, + List subclassMappings, + Map presenceChecksByParameter, + Type subclassExhaustiveException, + Map sourceParametersReassignments + ) { + super( + method, + annotations, + existingVariableNames, + factoryMethod, + mapNullToDefault, + beforeMappingReferences, + afterMappingReferences + ); + //CHECKSTYLE:ON + this.propertyMappings = propertyMappings; + this.returnTypeBuilder = returnTypeBuilder; + this.finalizerMethod = finalizerMethod; + this.subclassExhaustiveException = subclassExhaustiveException; + if ( this.finalizerMethod != null ) { + this.finalizedResultName = + Strings.getSafeVariableName( getResultName() + "Result", existingVariableNames ); + existingVariableNames.add( this.finalizedResultName ); + this.optionalResultName = + Strings.getSafeVariableName( getResultName() + "ResultOptional", existingVariableNames ); + existingVariableNames.add( this.optionalResultName ); + } + else { + this.finalizedResultName = null; + this.optionalResultName = + Strings.getSafeVariableName( getResultName() + "Optional", existingVariableNames ); + existingVariableNames.add( this.optionalResultName ); + } + this.mappingReferences = mappingReferences; - // intialize constant mappings as all mappings, but take out the ones that can be contributed to a + this.beforeMappingReferencesWithFinalizedReturnType = beforeMappingReferencesWithFinalizedReturnType; + this.afterMappingReferencesWithFinalizedReturnType = afterMappingReferencesWithFinalizedReturnType; + this.afterMappingReferencesWithOptionalReturnType = afterMappingReferencesWithOptionalReturnType; + + // initialize constant mappings as all mappings, but take out the ones that can be contributed to a // parameter mapping. - this.mappingsByParameter = new HashMap>(); - this.constantMappings = new ArrayList( propertyMappings ); + this.mappingsByParameter = new HashMap<>(); + this.constantMappings = new ArrayList<>( propertyMappings.size() ); + this.presenceChecksByParameter = presenceChecksByParameter; + this.constructorMappingsByParameter = new LinkedHashMap<>(); + this.constructorConstantMappings = new ArrayList<>(); + Set sourceParameterNames = new HashSet<>(); for ( Parameter sourceParameter : getSourceParameters() ) { - ArrayList mappingsOfParameter = new ArrayList(); - mappingsByParameter.put( sourceParameter.getName(), mappingsOfParameter ); - for ( PropertyMapping mapping : propertyMappings ) { - if ( sourceParameter.getName().equals( mapping.getSourceBeanName() ) ) { - mappingsOfParameter.add( mapping ); - constantMappings.remove( mapping ); + sourceParameterNames.add( sourceParameter.getName() ); + } + for ( PropertyMapping mapping : propertyMappings ) { + if ( mapping.isConstructorMapping() ) { + if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { + constructorMappingsByParameter.computeIfAbsent( + mapping.getSourceBeanName(), + key -> new ArrayList<>() + ).add( mapping ); + } + else { + constructorConstantMappings.add( mapping ); } } + else if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) { + mappingsByParameter.computeIfAbsent( mapping.getSourceBeanName(), key -> new ArrayList<>() ) + .add( mapping ); + } + else { + constantMappings.add( mapping ); + } } - this.factoryMethod = factoryMethod; - this.mapNullToDefault = mapNullToDefault; - this.resultType = resultType; + this.returnTypeToConstruct = returnTypeToConstruct; + this.newInstance = ( returnTypeToConstruct != null && getFactoryMethod() == null ) + ? NewInstanceCreation.forType( returnTypeToConstruct ) + : null; + this.subclassMappings = subclassMappings; + this.sourceParametersReassignments = sourceParametersReassignments; } - public List getPropertyMappings() { - return propertyMappings; + public Type getSubclassExhaustiveException() { + return subclassExhaustiveException; } public List getConstantMappings() { return constantMappings; } - public Map> getPropertyMappingsByParameter() { - return mappingsByParameter; + public List getConstructorConstantMappings() { + return constructorConstantMappings; } - public boolean isMapNullToDefault() { - return mapNullToDefault; + public List getSubclassMappings() { + return subclassMappings; } - @Override - public Type getResultType() { - if ( resultType == null ) { - return super.getResultType(); - } - else { - return resultType; + public String getFinalizedResultName() { + return finalizedResultName; + } + + public Type getFinalizedReturnType() { + Type returnType = getReturnType(); + if ( returnType.isOptionalType() ) { + return returnType.getOptionalBaseType(); } + return returnType; + } + + public String getOptionalResultName() { + return optionalResultName; + } + + public List getBeforeMappingReferencesWithFinalizedReturnType() { + return beforeMappingReferencesWithFinalizedReturnType; + } + + public List getAfterMappingReferencesWithFinalizedReturnType() { + return afterMappingReferencesWithFinalizedReturnType; + } + + public List getAfterMappingReferencesWithOptionalReturnType() { + return afterMappingReferencesWithOptionalReturnType; + } + + public List propertyMappingsByParameter(Parameter parameter) { + // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value + return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); + } + + public List constructorPropertyMappingsByParameter(Parameter parameter) { + // issues: #909 and #1244. FreeMarker has problem getting values from a map when the search key is size or value + return constructorMappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); + } + + public Type getReturnTypeToConstruct() { + return returnTypeToConstruct; + } + + public NewInstanceCreation getNewInstance() { + return newInstance; + } + + public boolean hasSubclassMappings() { + return !subclassMappings.isEmpty(); + } + + public boolean isAbstractReturnType() { + return getFactoryMethod() == null && returnTypeToConstruct != null + && returnTypeToConstruct.isAbstract(); + } + + public boolean hasConstructorMappings() { + return !constructorMappingsByParameter.isEmpty() || !constructorConstantMappings.isEmpty(); + } + + public MethodReference getFinalizerMethod() { + return finalizerMethod; } @Override @@ -643,34 +2293,123 @@ public Set getImportTypes() { for ( PropertyMapping propertyMapping : propertyMappings ) { types.addAll( propertyMapping.getImportTypes() ); + if ( propertyMapping.isConstructorMapping() ) { + // We need to add the target type imports for a constructor mapper since we define its parameters + types.addAll( propertyMapping.getTargetType().getImportTypes() ); + } + } + for ( SubclassMapping subclassMapping : subclassMappings ) { + types.addAll( subclassMapping.getImportTypes() ); + } + + if ( returnTypeToConstruct != null ) { + types.addAll( newInstance != null ? newInstance.getImportTypes() : returnTypeToConstruct.getImportTypes() ); + } + if ( returnTypeBuilder != null ) { + types.add( returnTypeBuilder.getOwningType() ); + } + for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithFinalizedReturnType ) { + types.addAll( reference.getImportTypes() ); + } + for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithFinalizedReturnType ) { + types.addAll( reference.getImportTypes() ); + } + + for ( LifecycleCallbackMethodReference reference : afterMappingReferencesWithOptionalReturnType ) { + types.addAll( reference.getImportTypes() ); } return types; } - public List getSourceParametersExcludingPrimitives() { - List sourceParameters = new ArrayList(); - for ( Parameter sourceParam : getSourceParameters() ) { - if ( !sourceParam.getType().isPrimitive() ) { - sourceParameters.add( sourceParam ); - } + public Collection getSourcePresenceChecks() { + return presenceChecksByParameter.values(); + } + + public Map getPresenceChecksByParameter() { + return presenceChecksByParameter; + } + + public PresenceCheck getPresenceCheckByParameter(Parameter parameter) { + return presenceChecksByParameter.get( parameter.getName() ); + } + + public List getSourceParametersNeedingPresenceCheck() { + return getSourceParameters().stream() + .filter( this::needsPresenceCheck ) + .collect( Collectors.toList() ); + } + + public List getSourceParametersNotNeedingPresenceCheck() { + return getSourceParameters().stream() + .filter( parameter -> !needsPresenceCheck( parameter ) ) + .collect( Collectors.toList() ); + } + + public Parameter getSourceParameterReassignment(Parameter parameter) { + return sourceParametersReassignments.get( parameter.getName() ); + } + + private boolean needsPresenceCheck(Parameter parameter) { + if ( !presenceChecksByParameter.containsKey( parameter.getName() ) ) { + return false; + } + + List mappings = propertyMappingsByParameter( parameter ); + if ( mappings.size() == 1 && doesNotNeedPresenceCheckForSourceParameter( mappings.get( 0 ) ) ) { + return false; } - return sourceParameters; + mappings = constructorPropertyMappingsByParameter( parameter ); + + if ( mappings.size() == 1 && doesNotNeedPresenceCheckForSourceParameter( mappings.get( 0 ) ) ) { + return false; + } + + return true; } - public List getSourcePrimitiveParameters() { - List sourceParameters = new ArrayList(); - for ( Parameter sourceParam : getSourceParameters() ) { - if ( sourceParam.getType().isPrimitive() ) { - sourceParameters.add( sourceParam ); - } + private boolean doesNotNeedPresenceCheckForSourceParameter(PropertyMapping mapping) { + if ( mapping.getAssignment().isCallingUpdateMethod() ) { + // If the mapping assignment is calling an update method then we should do a null check + // in the bean mapping + return false; } - return sourceParameters; + + return mapping.getAssignment().isSourceReferenceParameter(); + } + + @Override + public int hashCode() { + //Needed for Checkstyle, otherwise it fails due to EqualsHashCode rule + return super.hashCode(); } + @Override + public boolean equals(Object obj) { + + if ( this == obj ) { + return true; + } + if ( obj == null || getClass() != obj.getClass() ) { + return false; + } + + BeanMappingMethod that = (BeanMappingMethod) obj; + + if ( !super.equals( obj ) ) { + return false; + } + + if ( !Objects.equals( propertyMappings, that.propertyMappings ) ) { + return false; + } + + if ( !Objects.equals( mappingReferences, that.mappingReferences ) ) { + return false; + } + - public MethodReference getFactoryMethod() { - return this.factoryMethod; + return true; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java new file mode 100644 index 0000000000..848e2cc35f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java @@ -0,0 +1,96 @@ +/* + * 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.model; + +import java.util.Collection; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; + +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.model.common.BuilderType; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.util.Extractor; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * Factory for creating the appropriate builder finisher method. + * + * @author Filip Hrisafov + */ +public class BuilderFinisherMethodResolver { + + private static final String DEFAULT_BUILD_METHOD_NAME = "build"; + + private static final Extractor EXECUTABLE_ELEMENT_NAME_EXTRACTOR = + executableElement -> { + StringBuilder sb = new StringBuilder( executableElement.getSimpleName() ); + + sb.append( '(' ); + for ( VariableElement parameter : executableElement.getParameters() ) { + sb.append( parameter ); + } + + sb.append( ')' ); + return sb.toString(); + }; + + private BuilderFinisherMethodResolver() { + } + + public static MethodReference getBuilderFinisherMethod(Method method, BuilderType builderType, + MappingBuilderContext ctx) { + Collection buildMethods = builderType.getBuildMethods(); + if ( buildMethods.isEmpty() ) { + //If we reach this method this should never happen + return null; + } + + BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); + if ( builder == null && buildMethods.size() == 1 ) { + return MethodReference.forMethodCall( first( buildMethods ).getSimpleName().toString() ); + } + else { + String buildMethodPattern = DEFAULT_BUILD_METHOD_NAME; + if ( builder != null ) { + buildMethodPattern = builder.buildMethod().get(); + } + for ( ExecutableElement buildMethod : buildMethods ) { + String methodName = buildMethod.getSimpleName().toString(); + if ( methodName.matches( buildMethodPattern ) ) { + return MethodReference.forMethodCall( methodName ); + } + } + + if ( builder == null ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.BUILDER_NO_BUILD_METHOD_FOUND_DEFAULT, + buildMethodPattern, + builderType.getBuilder(), + builderType.getBuildingType(), + Strings.join( buildMethods, ", ", EXECUTABLE_ELEMENT_NAME_EXTRACTOR ) + ); + } + else { + ctx.getMessager().printMessage( + method.getExecutable(), + builder.mirror(), + Message.BUILDER_NO_BUILD_METHOD_FOUND, + buildMethodPattern, + builderType.getBuilder(), + builderType.getBuildingType(), + Strings.join( buildMethods, ", ", EXECUTABLE_ELEMENT_NAME_EXTRACTOR ) + ); + } + } + + return null; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java new file mode 100644 index 0000000000..5c99b85588 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java @@ -0,0 +1,345 @@ +/* + * 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.model; + +import java.util.function.Predicate; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; + +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.model.assignment.ExistingInstanceSetterWrapperForCollectionsAndMaps; +import org.mapstruct.ap.internal.model.assignment.GetterWrapperForCollectionsAndMaps; +import org.mapstruct.ap.internal.model.assignment.NewInstanceSetterWrapperForCollectionsAndMaps; +import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMaps; +import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMapsWithNullCheck; +import org.mapstruct.ap.internal.model.assignment.UpdateWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Assignment.AssignmentType; +import org.mapstruct.ap.internal.model.common.SourceRHS; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.NullabilityResolver; +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; + +import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_NULL; + +/** + * A builder that is used for creating an assignment to a collection. + * + * The created assignments to the following null checks: + *

      + *
    • source-null-check - For this the {@link SetterWrapperForCollectionsAndMapsWithNullCheck} is used when a + * direct assignment is done or the {@link org.mapstruct.NullValueCheckStrategy} is + * {@link org.mapstruct.NullValueCheckStrategy#ALWAYS}. It is also done in + * {@link ExistingInstanceSetterWrapperForCollectionsAndMaps} which extends + * {@link SetterWrapperForCollectionsAndMapsWithNullCheck}
    • + *
    • target-null-check - Done in the {@link ExistingInstanceSetterWrapperForCollectionsAndMaps}
    • + *
    • local-var-null-check - Done in {@link ExistingInstanceSetterWrapperForCollectionsAndMaps}, and + * {@link SetterWrapperForCollectionsAndMapsWithNullCheck}
    • + *
    + * + * A local-var-null-check is needed in the following cases: + * + *
      + *
    • Presence check with direct assignment - We need a null check before setting, because we use the copy + * constructor
    • + *
    • Presence check for existing instance mapping - We need the null check because we call addAll / putAll.
    • + *
    • No Presence check and direct assignment - We use the copy constructor
    • + *
    • No Presence check and {@link org.mapstruct.NullValueCheckStrategy#ALWAYS} - the user requested one
    • + *
    + * + * @author Filip Hrisafov + */ +public class CollectionAssignmentBuilder { + private MappingBuilderContext ctx; + private Method method; + private Accessor targetReadAccessor; + private Type targetType; + private String targetPropertyName; + private AccessorType targetAccessorType; + private Assignment assignment; + private SourceRHS sourceRHS; + private NullValueCheckStrategyGem nvcs; + private NullValuePropertyMappingStrategyGem nvpms; + private NullabilityResolver.Nullability sourceJSpecifyNullability = NullabilityResolver.Nullability.UNKNOWN; + + public CollectionAssignmentBuilder mappingBuilderContext(MappingBuilderContext ctx) { + this.ctx = ctx; + return this; + } + + public CollectionAssignmentBuilder method(Method method) { + this.method = method; + return this; + } + + public CollectionAssignmentBuilder targetReadAccessor(Accessor targetReadAccessor) { + this.targetReadAccessor = targetReadAccessor; + return this; + } + + public CollectionAssignmentBuilder targetType(Type targetType) { + this.targetType = targetType; + return this; + } + + public CollectionAssignmentBuilder targetPropertyName(String targetPropertyName) { + this.targetPropertyName = targetPropertyName; + return this; + } + + public CollectionAssignmentBuilder targetAccessorType(AccessorType targetAccessorType) { + this.targetAccessorType = targetAccessorType; + return this; + } + + /** + * @param assignment the assignment that needs to be invoked + * + * @return this builder for chaining + */ + public CollectionAssignmentBuilder assignment(Assignment assignment) { + this.assignment = assignment; + return this; + } + + /** + * @param sourceRHS the source right hand side for getting the property for mapping + * + * @return this builder for chaining + */ + public CollectionAssignmentBuilder rightHandSide(SourceRHS sourceRHS) { + this.sourceRHS = sourceRHS; + return this; + } + + public CollectionAssignmentBuilder nullValueCheckStrategy( NullValueCheckStrategyGem nvcs ) { + this.nvcs = nvcs; + return this; + } + + public CollectionAssignmentBuilder nullValuePropertyMappingStrategy( NullValuePropertyMappingStrategyGem nvpms ) { + this.nvpms = nvpms; + return this; + } + + public CollectionAssignmentBuilder sourceJSpecifyNullability( + NullabilityResolver.Nullability sourceJSpecifyNullability + ) { + this.sourceJSpecifyNullability = sourceJSpecifyNullability != null + ? sourceJSpecifyNullability + : NullabilityResolver.Nullability.UNKNOWN; + return this; + } + + public Assignment build() { + Assignment result = assignment; + + CollectionMappingStrategyGem cms = method.getOptions().getMapper().getCollectionMappingStrategy(); + boolean targetImmutable = cms == CollectionMappingStrategyGem.TARGET_IMMUTABLE || targetReadAccessor == null; + + if ( targetAccessorType == AccessorType.SETTER || targetAccessorType.isFieldAssignment() ) { + + if ( result.isCallingUpdateMethod() && !targetImmutable ) { + + // call to an update method + if ( targetReadAccessor == null ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE, + targetPropertyName + ); + } + + Assignment factoryMethod = ObjectFactoryMethodResolver + .getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( sourceRHS ), ctx ); + result = new UpdateWrapper( + result, + method.getThrownTypes(), + factoryMethod, + targetAccessorType.isFieldAssignment(), + targetType, + true, + nvpms == SET_TO_NULL && !targetType.isPrimitive(), + nvpms == SET_TO_DEFAULT, + false + ); + } + else if ( method.isUpdateMethod() && !targetImmutable ) { + + result = new ExistingInstanceSetterWrapperForCollectionsAndMaps( + result, + method.getThrownTypes(), + targetType, + nvcs, + nvpms, + ctx.getTypeFactory(), + targetAccessorType.isFieldAssignment() + ); + } + else if ( method.isUpdateMethod() && nvpms == IGNORE ) { + + result = new SetterWrapperForCollectionsAndMapsWithNullCheck( + result, + method.getThrownTypes(), + targetType, + ctx.getTypeFactory(), + targetAccessorType.isFieldAssignment() + ); + } + else if ( setterWrapperNeedsSourceNullCheck( result ) + && canBeMappedOrDirectlyAssigned( result ) ) { + + result = new SetterWrapperForCollectionsAndMapsWithNullCheck( + result, + method.getThrownTypes(), + targetType, + ctx.getTypeFactory(), + targetAccessorType.isFieldAssignment() + ); + } + else if ( canBeMappedOrDirectlyAssigned( result ) ) { + //TODO init default value + + // target accessor is setter, so wrap the setter in setter map/ collection handling + result = new SetterWrapperForCollectionsAndMaps( + result, + method.getThrownTypes(), + targetType, + targetAccessorType.isFieldAssignment() + ); + } + else if ( hasNoArgsConstructor() ) { + result = new NewInstanceSetterWrapperForCollectionsAndMaps( + result, + method.getThrownTypes(), + targetType, + ctx.getTypeFactory(), + targetAccessorType.isFieldAssignment() ); + } + else { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR, + targetType + ); + } + } + else { + if ( targetImmutable ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.PROPERTYMAPPING_NO_WRITE_ACCESSOR_FOR_TARGET_TYPE, + targetPropertyName + ); + } + + // target accessor is getter, so wrap the setter in getter map/ collection handling + result = new GetterWrapperForCollectionsAndMaps( + result, + method.getThrownTypes(), + targetType, + nvpms, + targetAccessorType.isFieldAssignment() + ); + } + + return result; + } + + private boolean canBeMappedOrDirectlyAssigned(Assignment result) { + return result.getType() != AssignmentType.DIRECT + || hasCopyConstructor() + || targetType.isEnumSet(); + } + + /** + * Checks whether the setter wrapper should include a null / presence check or not + * + * @param rhs the source right hand side + * @return whether to include a null / presence check or not + */ + private boolean setterWrapperNeedsSourceNullCheck(Assignment rhs) { + // JSpecify: source @NonNull means the value is guaranteed non-null, skip the wrapper + if ( sourceJSpecifyNullability == NullabilityResolver.Nullability.NON_NULL ) { + ctx.getMessager().note( 2, + Message.PROPERTYMAPPING_JSPECIFY_SKIP_NULL_CHECK_NON_NULL_SOURCE, + targetPropertyName ); + return false; + } + + if ( rhs.getSourcePresenceCheckerReference() != null ) { + // If there is a source presence check then we should do a null check + return true; + } + + if ( nvcs == ALWAYS ) { + // NullValueCheckStrategy is ALWAYS -> do a null check + return true; + } + + if ( rhs.getType().isDirect() ) { + return true; + } + + return false; + } + + private boolean hasCopyConstructor() { + return checkConstructorForPredicate( this::hasCopyConstructor ); + } + + private boolean hasNoArgsConstructor() { + return checkConstructorForPredicate( this::hasNoArgsConstructor ); + } + + private boolean checkConstructorForPredicate(Predicate predicate) { + if ( targetType.isCollectionOrMapType() ) { + if ( "java.util".equals( targetType.getPackageName() ) ) { + return true; + } + else { + Element sourceElement = targetType.getImplementationType() != null + ? targetType.getImplementationType().getTypeElement() + : targetType.getTypeElement(); + if ( sourceElement != null ) { + for ( Element element : sourceElement.getEnclosedElements() ) { + if ( element.getKind() == ElementKind.CONSTRUCTOR + && element.getModifiers().contains( Modifier.PUBLIC ) ) { + if ( predicate.test( element ) ) { + return true; + } + } + } + } + } + } + return false; + } + + private boolean hasNoArgsConstructor(Element element) { + return ( (ExecutableElement) element ).getParameters().isEmpty(); + } + + private boolean hasCopyConstructor(Element element) { + if ( element instanceof ExecutableElement ) { + ExecutableElement ee = (ExecutableElement) element; + return ee.getParameters().size() == 1 + && ctx.getTypeUtils().isAssignable( targetType.getTypeMirror(), ee.getParameters().get( 0 ).asType() ); + } + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Constructor.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Constructor.java index b57cb9e3bd..a352a36063 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Constructor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Constructor.java @@ -1,25 +1,16 @@ -/** - * Copyright 2012-2015 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.model; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Type; + /** - * Basic interface class that facilitates an empty constructor + * Basic interface class that facilitates an empty constructor. * * @author Sjaak Derksen */ @@ -27,4 +18,6 @@ public interface Constructor { String getName(); + Set getImportTypes(); + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java new file mode 100644 index 0000000000..1a807725d3 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java @@ -0,0 +1,149 @@ +/* + * 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.model; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.util.Strings; + +/** + * A {@link MappingMethod} implemented by a {@link Mapper} class which does mapping of generic types. + * For example Iterable or Stream. + * The generic elements are mapped either by a {@link TypeConversion} or another mapping method. + * + * @author Filip Hrisafov + */ +public abstract class ContainerMappingMethod extends NormalTypeMappingMethod { + private final Assignment elementAssignment; + private final String loopVariableName; + private final SelectionParameters selectionParameters; + private final String index1Name; + private final String index2Name; + private final Parameter sourceParameter; + private final PresenceCheck sourceParameterPresenceCheck; + private IterableCreation iterableCreation; + + //CHECKSTYLE:OFF + ContainerMappingMethod(Method method, List annotations, + Collection existingVariables, Assignment parameterAssignment, + MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, + List beforeMappingReferences, + List afterMappingReferences, + SelectionParameters selectionParameters, PresenceCheck sourceParameterPresenceCheck) { + //CHECKSTYLE:ON + super( method, annotations, existingVariables, factoryMethod, mapNullToDefault, beforeMappingReferences, + afterMappingReferences ); + this.elementAssignment = parameterAssignment; + this.loopVariableName = loopVariableName; + this.selectionParameters = selectionParameters != null ? selectionParameters : SelectionParameters.empty(); + + this.index1Name = Strings.getSafeVariableName( "i", existingVariables ); + this.index2Name = Strings.getSafeVariableName( "j", existingVariables ); + + Parameter sourceParameter = null; + for ( Parameter parameter : getParameters() ) { + if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { + sourceParameter = parameter; + break; + } + } + + if ( sourceParameter == null ) { + throw new IllegalStateException( "Method " + this + " has no source parameter." ); + } + + this.sourceParameter = sourceParameter; + this.sourceParameterPresenceCheck = sourceParameterPresenceCheck; + + } + + public Parameter getSourceParameter() { + return sourceParameter; + } + + public PresenceCheck getSourceParameterPresenceCheck() { + return sourceParameterPresenceCheck; + } + + public IterableCreation getIterableCreation() { + if ( iterableCreation == null ) { + iterableCreation = IterableCreation.create( this, getSourceParameter() ); + } + return iterableCreation; + } + + public Assignment getElementAssignment() { + return elementAssignment; + } + + @Override + public Set getImportTypes() { + Set types = super.getImportTypes(); + if ( elementAssignment != null ) { + types.addAll( elementAssignment.getImportTypes() ); + } + + if ( iterableCreation != null ) { + types.addAll( iterableCreation.getImportTypes() ); + } + return types; + } + + public String getLoopVariableName() { + return loopVariableName; + } + + public abstract Type getResultElementType(); + + public String getIndex1Name() { + return index1Name; + } + + public String getIndex2Name() { + return index2Name; + } + + @Override + public int hashCode() { + //Needed for Checkstyle, otherwise it fails due to EqualsHashCode rule + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + + if ( !super.equals( obj ) ) { + return false; + } + + ContainerMappingMethod other = (ContainerMappingMethod) obj; + + if ( !Objects.equals( selectionParameters, other.selectionParameters ) ) { + return false; + } + + return true; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java new file mode 100644 index 0000000000..598db971e2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java @@ -0,0 +1,206 @@ +/* + * 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.model; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.SourceRHS; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * Builder that can be used to build {@link ContainerMappingMethod}(s). + * + * @param the builder itself that needs to be used for chaining + * @param the method that the builder builds + * + * @author Filip Hrisafov + */ +public abstract class ContainerMappingMethodBuilder, + M extends ContainerMappingMethod> extends AbstractMappingMethodBuilder { + + private SelectionParameters selectionParameters; + private FormattingParameters formattingParameters; + private String errorMessagePart; + private String callingContextTargetPropertyName; + private AnnotationMirror positionHint; + + ContainerMappingMethodBuilder(Class selfType, String errorMessagePart) { + super( selfType ); + this.errorMessagePart = errorMessagePart; + } + + public B formattingParameters(FormattingParameters formattingParameters) { + this.formattingParameters = formattingParameters; + return myself; + } + + public B selectionParameters(SelectionParameters selectionParameters) { + this.selectionParameters = selectionParameters; + return myself; + } + + public B callingContextTargetPropertyName(String callingContextTargetPropertyName) { + this.callingContextTargetPropertyName = callingContextTargetPropertyName; + return myself; + } + + public B positionHint(AnnotationMirror positionHint) { + this.positionHint = positionHint; + return myself; + } + + @Override + public final M build() { + Type sourceParameterType = first( method.getSourceParameters() ).getType(); + Type resultType = method.getResultType(); + + Type sourceElementType = getElementType( sourceParameterType ); + Type targetElementType = getElementType( resultType ); + + String loopVariableName = + Strings.getSafeVariableName( sourceElementType.getName(), method.getParameterNames() ); + + SourceRHS sourceRHS = new SourceRHS( + loopVariableName, + sourceElementType, + new HashSet<>(), + errorMessagePart + ); + + SelectionCriteria criteria = SelectionCriteria.forMappingMethods( selectionParameters, + method.getOptions().getIterableMapping().getMappingControl( ctx.getElementUtils() ), + callingContextTargetPropertyName, + false + ); + + Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method, + getDescription(), + targetElementType, + formattingParameters, + criteria, + sourceRHS, + positionHint, + () -> forge( sourceRHS, sourceElementType, targetElementType ) + ); + + if ( assignment == null ) { + if ( method instanceof ForgedMethod ) { + // leave messaging to calling property mapping + return null; + } + else { + reportCannotCreateMapping( + method, + String.format( "%s \"%s\"", sourceRHS.getSourceErrorMessagePart(), + sourceRHS.getSourceType().describe() ), + sourceRHS.getSourceType(), + targetElementType, + "" + ); + } + } + else { + ctx.getMessager().note( 2, Message.ITERABLEMAPPING_SELECT_ELEMENT_NOTE, assignment ); + if ( method instanceof ForgedMethod ) { + ForgedMethod forgedMethod = (ForgedMethod) method; + forgedMethod.addThrownTypes( assignment.getThrownTypes() ); + } + } + assignment = getWrapper( assignment, method ); + + // mapNullToDefault — a JSpecify @NonNull return forces RETURN_DEFAULT to avoid generating `return null`. + // Forcing is unconditional here (unlike BeanMappingMethod): when the source is @NonNull the template skips + // the whole guard block via `sourceParameterPresenceCheck??`, so the forced value is simply unused there. + boolean mapNullToDefault = method.getOptions() + .getIterableMapping() + .getNullValueMappingStrategy() + .isReturnDefault(); + if ( !mapNullToDefault && ctx.isJSpecifyNonNullReturn( method ) ) { + ctx.getMessager().note( 2, + Message.MAPPING_METHOD_JSPECIFY_FORCE_RETURN_DEFAULT, + method.getName() ); + mapNullToDefault = true; + } + + MethodReference factoryMethod = null; + if ( !method.isUpdateMethod() ) { + factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method, null, ctx ); + } + + Set existingVariables = new HashSet<>( method.getParameterNames() ); + existingVariables.add( loopVariableName ); + + List beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods( + method, + selectionParameters, + ctx, + existingVariables + ); + List afterMappingMethods = LifecycleMethodResolver.afterMappingMethods( + method, + selectionParameters, + ctx, + existingVariables + ); + + // Resolve presence check via JSpecify-aware resolver — returns null when source is @NonNull. + Parameter sourceParam = first( method.getSourceParameters() ); + PresenceCheck sourceParameterPresenceCheck = + PresenceCheckMethodResolver.getPresenceCheckForSourceParameter( method, null, sourceParam, ctx ); + + return instantiateMappingMethod( + method, + existingVariables, + assignment, + factoryMethod, + mapNullToDefault, + loopVariableName, + beforeMappingMethods, + afterMappingMethods, + selectionParameters, + sourceParameterPresenceCheck + ); + } + + private Assignment forge(SourceRHS sourceRHS, Type sourceType, Type targetType) { + Assignment assignment = super.forgeMapping( sourceRHS, sourceType, targetType ); + if ( assignment != null ) { + ctx.getMessager().note( 2, Message.ITERABLEMAPPING_CREATE_ELEMENT_NOTE, assignment ); + } + return assignment; + } + + protected abstract M instantiateMappingMethod(Method method, Collection existingVariables, + Assignment assignment, MethodReference factoryMethod, + boolean mapNullToDefault, String loopVariableName, + List beforeMappingMethods, + List afterMappingMethods, + SelectionParameters selectionParameters, PresenceCheck sourceParameterPresenceCheck); + + protected abstract Type getElementType(Type parameterType); + + protected abstract Assignment getWrapper(Assignment assignment, Method method); + + @Override + protected boolean shouldUsePropertyNamesInHistory() { + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java index 454b9b6005..df7940da31 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Decorator.java @@ -1,37 +1,23 @@ -/** - * Copyright 2012-2015 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.model; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.internal.gem.DecoratedWithGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.option.Options; -import org.mapstruct.ap.internal.prism.DecoratedWithPrism; import org.mapstruct.ap.internal.version.VersionInformation; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import java.util.Arrays; -import java.util.List; -import java.util.SortedSet; - /** * Represents a decorator applied to a generated mapper type. * @@ -39,28 +25,19 @@ */ public class Decorator extends GeneratedType { - public static class Builder { + public static class Builder extends GeneratedTypeBuilder { - private Elements elementUtils; - private TypeFactory typeFactory; private TypeElement mapperElement; - private DecoratedWithPrism decoratorPrism; - private List methods; - private Options options; - private VersionInformation versionInformation; + private DecoratedWithGem decorator; + private boolean hasDelegateConstructor; private String implName; private String implPackage; - private SortedSet extraImportedTypes; + private boolean suppressGeneratorTimestamp; + private Set customAnnotations; - public Builder elementUtils(Elements elementUtils) { - this.elementUtils = elementUtils; - return this; - } - - public Builder typeFactory(TypeFactory typeFactory) { - this.typeFactory = typeFactory; - return this; + public Builder() { + super( Builder.class ); } public Builder mapperElement(TypeElement mapperElement) { @@ -68,23 +45,8 @@ public Builder mapperElement(TypeElement mapperElement) { return this; } - public Builder decoratorPrism(DecoratedWithPrism decoratorPrism) { - this.decoratorPrism = decoratorPrism; - return this; - } - - public Builder methods(List methods) { - this.methods = methods; - return this; - } - - public Builder options(Options options) { - this.options = options; - return this; - } - - public Builder versionInformation(VersionInformation versionInformation) { - this.versionInformation = versionInformation; + public Builder decoratedWith(DecoratedWithGem decoratedGem) { + this.decorator = decoratedGem; return this; } @@ -103,23 +65,29 @@ public Builder implPackage(String implPackage) { return this; } - public Builder extraImports(SortedSet extraImportedTypes) { - this.extraImportedTypes = extraImportedTypes; + public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { + this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; + return this; + } + + public Builder additionalAnnotations(Set customAnnotations) { + this.customAnnotations = customAnnotations; return this; } public Decorator build() { String implementationName = implName.replace( Mapper.CLASS_NAME_PLACEHOLDER, - mapperElement.getSimpleName() ); + Mapper.getFlatName( mapperElement ) ); - Type decoratorType = typeFactory.getType( decoratorPrism.value() ); + Type decoratorType = typeFactory.getType( decorator.value().get() ); DecoratorConstructor decoratorConstructor = new DecoratorConstructor( - implementationName, - implementationName + "_", - hasDelegateConstructor ); + implementationName, + implementationName + "_", + hasDelegateConstructor ); - String elementPackage = elementUtils.getPackageOf( mapperElement ).getQualifiedName().toString(); + Type mapperType = typeFactory.getType( mapperElement ); + String elementPackage = mapperType.getPackageName(); String packageName = implPackage.replace( Mapper.PACKAGE_NAME_PLACEHOLDER, elementPackage ); return new Decorator( @@ -127,50 +95,74 @@ public Decorator build() { packageName, implementationName, decoratorType, - elementPackage, - mapperElement.getKind() == ElementKind.INTERFACE ? mapperElement.getSimpleName().toString() : null, + mapperType, methods, - Arrays.asList( new Field( typeFactory.getType( mapperElement ), "delegate", true ) ) , options, versionInformation, + suppressGeneratorTimestamp, Accessibility.fromModifiers( mapperElement.getModifiers() ), extraImportedTypes, - decoratorConstructor + decoratorConstructor, + customAnnotations ); } } private final Type decoratorType; + private final Type mapperType; @SuppressWarnings( "checkstyle:parameternumber" ) private Decorator(TypeFactory typeFactory, String packageName, String name, Type decoratorType, - String interfacePackage, String interfaceName, List methods, - List fields, Options options, VersionInformation versionInformation, + Type mapperType, + List methods, + Options options, VersionInformation versionInformation, + boolean suppressGeneratorTimestamp, Accessibility accessibility, SortedSet extraImports, - DecoratorConstructor decoratorConstructor) { + DecoratorConstructor decoratorConstructor, + Set customAnnotations) { super( typeFactory, packageName, name, - decoratorType.getName(), - interfacePackage, - interfaceName, + decoratorType, methods, - fields, + Arrays.asList( new Field( mapperType, "delegate", true ) ), options, versionInformation, + suppressGeneratorTimestamp, accessibility, extraImports, decoratorConstructor ); this.decoratorType = decoratorType; + this.mapperType = mapperType; + + // Add custom annotations + if ( customAnnotations != null ) { + customAnnotations.forEach( this::addAnnotation ); + } } @Override public SortedSet getImportTypes() { SortedSet importTypes = super.getImportTypes(); - addWithDependents( importTypes, decoratorType ); + // DecoratorType needs special handling in case it is nested + // calling addIfImportRequired is not the most correct approach since it would + // lead to checking if the type is to be imported and that would be false + // since the Decorator is a nested class within the Mapper. + // However, when generating the Decorator this is not needed, + // because the Decorator is a top level class itself + // In a nutshell creating the Decorator should have its own ProcessorContext, but it doesn't + if ( decoratorType.getPackageName().equalsIgnoreCase( getPackageName() ) ) { + if ( decoratorType.getTypeElement() != null && + decoratorType.getTypeElement().getNestingKind().isNested() ) { + importTypes.add( decoratorType ); + } + } + else { + importTypes.add( decoratorType ); + } return importTypes; } @@ -178,4 +170,8 @@ public SortedSet getImportTypes() { protected String getTemplateName() { return getTemplateNameForClass( GeneratedType.class ); } + + public Type getMapperType() { + return mapperType; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/DecoratorConstructor.java b/processor/src/main/java/org/mapstruct/ap/internal/model/DecoratorConstructor.java index 949f5a7204..3bdfd66435 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/DecoratorConstructor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/DecoratorConstructor.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java index ff4b1e0aad..2728f99323 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model; @@ -34,28 +21,31 @@ */ public class DefaultMapperReference extends MapperReference { + private final boolean isSingleton; private final boolean isAnnotatedMapper; private final Set importTypes; - private DefaultMapperReference(Type type, boolean isAnnotatedMapper, Set importTypes, String variableName) { + private DefaultMapperReference(Type type, boolean isAnnotatedMapper, boolean isSingleton, + Set importTypes, String variableName) { super( type, variableName ); this.isAnnotatedMapper = isAnnotatedMapper; this.importTypes = importTypes; + this.isSingleton = isSingleton; } - public static DefaultMapperReference getInstance(Type type, boolean isAnnotatedMapper, TypeFactory typeFactory, - List otherMapperReferences) { + public static DefaultMapperReference getInstance(Type type, boolean isAnnotatedMapper, boolean isSingleton, + TypeFactory typeFactory, List otherMapperReferences) { Set importTypes = Collections.asSet( type ); - if ( isAnnotatedMapper ) { + if ( isAnnotatedMapper && !isSingleton ) { importTypes.add( typeFactory.getType( "org.mapstruct.factory.Mappers" ) ); } - String variableName = Strings.getSaveVariableName( + String variableName = Strings.getSafeVariableName( type.getName(), otherMapperReferences ); - return new DefaultMapperReference( type, isAnnotatedMapper, importTypes, variableName ); + return new DefaultMapperReference( type, isAnnotatedMapper, isSingleton, importTypes, variableName ); } @Override @@ -66,4 +56,9 @@ public Set getImportTypes() { public boolean isAnnotatedMapper() { return isAnnotatedMapper; } + + public boolean isSingleton() { + return isSingleton; + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMappingExclusionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMappingExclusionProvider.java new file mode 100644 index 0000000000..c5a97cf860 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMappingExclusionProvider.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.model; + +import java.util.regex.Pattern; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.spi.MappingExclusionProvider; + +/** + * The default implementation of the {@link MappingExclusionProvider} service provider interface. + * + * With the default implementation, MapStruct will not consider classes in the {@code java} and {@code javax} package + * as source / target for an automatic sub-mapping. The only exception is the {@link java.util.Collection}, + * {@link java.util.Map} and {@link java.util.stream.Stream} types. + * + * @author Filip Hrisafov + * @since 1.2 + */ +class DefaultMappingExclusionProvider implements MappingExclusionProvider { + private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); + + @Override + public boolean isExcluded(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + return name.length() != 0 && isFullyQualifiedNameExcluded( name ); + } + + protected boolean isFullyQualifiedNameExcluded(Name name) { + return JAVA_JAVAX_PACKAGE.matcher( name ).matches(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/DelegatingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/DelegatingMethod.java index 613df08dc4..4d5b8efe17 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/DelegatingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/DelegatingMethod.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Direct.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Direct.java deleted file mode 100644 index a312adf9ac..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Direct.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model; - -import org.mapstruct.ap.internal.model.assignment.Assignment; -import org.mapstruct.ap.internal.model.common.ModelElement; -import org.mapstruct.ap.internal.model.common.Type; - -import java.util.Collections; -import java.util.List; -import java.util.Set; - -/** - * Direct Assignment. Just a source reference - * - * @author Sjaak Derksen - */ -public class Direct extends ModelElement implements Assignment { - - private final String sourceReference; - - public Direct( String sourceReference ) { - this.sourceReference = sourceReference; - } - - @Override - public String getSourceReference() { - return sourceReference; - } - - @Override - public Set getImportTypes() { - return Collections.emptySet(); - } - - @Override - public List getThrownTypes() { - return Collections.emptyList(); - } - - @Override - public void setAssignment( Assignment assignment ) { - throw new UnsupportedOperationException( "Not supported." ); - } - - @Override - public AssignmentType getType() { - return AssignmentType.DIRECT; - } - - @Override - public boolean isUpdateMethod() { - return false; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java deleted file mode 100644 index 57eca3966d..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java +++ /dev/null @@ -1,208 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model; - -import java.util.ArrayList; -import java.util.List; - -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.model.common.Parameter; -import org.mapstruct.ap.internal.model.source.EnumMapping; -import org.mapstruct.ap.internal.model.source.Mapping; -import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.prism.BeanMappingPrism; -import org.mapstruct.ap.internal.util.Message; -import org.mapstruct.ap.internal.util.Strings; - -import static org.mapstruct.ap.internal.util.Collections.first; - -/** - * A {@link MappingMethod} which maps one enum type to another, optionally configured by one or more - * {@link EnumMapping}s. - * - * @author Gunnar Morling - */ -public class EnumMappingMethod extends MappingMethod { - - private final List enumMappings; - - public static class Builder { - - private SourceMethod method; - private MappingBuilderContext ctx; - - public Builder mappingContext(MappingBuilderContext mappingContext) { - this.ctx = mappingContext; - return this; - } - - public Builder souceMethod(SourceMethod sourceMethod) { - this.method = sourceMethod; - return this; - } - - public EnumMappingMethod build() { - - if ( !reportErrorIfMappedEnumConstantsDontExist( method ) - || !reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped( method ) ) { - return null; - } - - List enumMappings = new ArrayList(); - - List sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants(); - - for ( String enumConstant : sourceEnumConstants ) { - List mappedConstants = method.getMappingBySourcePropertyName( enumConstant ); - - if ( mappedConstants.isEmpty() ) { - enumMappings.add( new EnumMapping( enumConstant, enumConstant ) ); - } - else if ( mappedConstants.size() == 1 ) { - enumMappings.add( - new EnumMapping( - enumConstant, first( mappedConstants ).getTargetName() - ) - ); - } - else { - List targetConstants = new ArrayList( mappedConstants.size() ); - for ( Mapping mapping : mappedConstants ) { - targetConstants.add( mapping.getTargetName() ); - } - ctx.getMessager().printMessage( method.getExecutable(), - Message.ENUMMAPPING_MULTIPLE_TARGETS, - enumConstant, - Strings.join( targetConstants, ", " ) - ); - } - } - - List qualifiers = getQualifiers( method ); - - List beforeMappingMethods = - LifecycleCallbackFactory.beforeMappingMethods( method, qualifiers, ctx ); - List afterMappingMethods = - LifecycleCallbackFactory.afterMappingMethods( method, qualifiers, ctx ); - - return new EnumMappingMethod( method, enumMappings, beforeMappingMethods, afterMappingMethods ); - } - - private static List getQualifiers(SourceMethod method) { - BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); - - if ( beanMappingPrism != null ) { - return beanMappingPrism.qualifiedBy(); - } - - return null; - } - - private boolean reportErrorIfMappedEnumConstantsDontExist(SourceMethod method) { - List sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants(); - List targetEnumConstants = method.getReturnType().getEnumConstants(); - - boolean foundIncorrectMapping = false; - - for ( List mappedConstants : method.getMappingOptions().getMappings().values() ) { - for ( Mapping mappedConstant : mappedConstants ) { - - if ( mappedConstant.getSourceName() == null ) { - ctx.getMessager().printMessage( method.getExecutable(), - mappedConstant.getMirror(), - Message.ENUMMAPPING_UNDEFINED_SOURCE - ); - foundIncorrectMapping = true; - } - else if ( !sourceEnumConstants.contains( mappedConstant.getSourceName() ) ) { - ctx.getMessager().printMessage( method.getExecutable(), - mappedConstant.getMirror(), - mappedConstant.getSourceAnnotationValue(), - Message.ENUMMAPPING_NON_EXISTING_CONSTANT, - mappedConstant.getSourceName(), - first( method.getSourceParameters() ).getType() - ); - foundIncorrectMapping = true; - } - if ( mappedConstant.getTargetName() == null ) { - ctx.getMessager().printMessage( method.getExecutable(), - mappedConstant.getMirror(), - Message.ENUMMAPPING_UNDEFINED_TARGET - ); - foundIncorrectMapping = true; - } - else if ( !targetEnumConstants.contains( mappedConstant.getTargetName() ) ) { - ctx.getMessager().printMessage( method.getExecutable(), - mappedConstant.getMirror(), - mappedConstant.getTargetAnnotationValue(), - Message.ENUMMAPPING_NON_EXISTING_CONSTANT, - mappedConstant.getTargetName(), - method.getReturnType() - ); - foundIncorrectMapping = true; - } - } - } - - return !foundIncorrectMapping; - } - - private boolean reportErrorIfSourceEnumConstantsWithoutCorrespondingTargetConstantAreNotMapped( - SourceMethod method) { - - List sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants(); - List targetEnumConstants = method.getReturnType().getEnumConstants(); - List unmappedSourceEnumConstants = new ArrayList(); - - for ( String sourceEnumConstant : sourceEnumConstants ) { - if ( !targetEnumConstants.contains( sourceEnumConstant ) - && method.getMappingBySourcePropertyName( sourceEnumConstant ).isEmpty() ) { - unmappedSourceEnumConstants.add( sourceEnumConstant ); - } - } - - if ( !unmappedSourceEnumConstants.isEmpty() ) { - ctx.getMessager().printMessage( method.getExecutable(), - Message.ENUMMAPPING_UNMAPPED_TARGETS, - Strings.join( unmappedSourceEnumConstants, ", " ) - ); - } - - return unmappedSourceEnumConstants.isEmpty(); - } - - } - - private EnumMappingMethod(Method method, List enumMappings, - List beforeMappingMethods, - List afterMappingMethods) { - super( method, beforeMappingMethods, afterMappingMethods ); - this.enumMappings = enumMappings; - } - - public List getEnumMappings() { - return enumMappings; - } - - public Parameter getSourceParameter() { - return first( getParameters() ); - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Field.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Field.java index e57a101473..b1d346d82c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Field.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Field.java @@ -1,24 +1,14 @@ -/** - * Copyright 2012-2015 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.model; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; +import java.util.Objects; import java.util.Set; import org.mapstruct.ap.internal.model.common.ModelElement; @@ -42,6 +32,7 @@ public Field(Type type, String variableName, boolean used) { this.used = used; this.typeRequiresImport = used; } + public Field(Type type, String variableName) { this.type = type; this.variableName = variableName; @@ -104,4 +95,34 @@ public void setTypeRequiresImport(boolean typeRequiresImport) { this.typeRequiresImport = typeRequiresImport; } + @Override + public int hashCode() { + int hash = 5; + hash = 43 * hash + (this.variableName != null ? this.variableName.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + final Field other = (Field) obj; + return Objects.equals( variableName, other.variableName ); + } + + public static List getFieldNames(Set fields) { + List names = new ArrayList<>( fields.size() ); + for ( Field field : fields ) { + names.add( field.getVariableName() ); + } + return names; + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java new file mode 100644 index 0000000000..fb7943ab24 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java @@ -0,0 +1,442 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.lang.model.element.ExecutableElement; + +import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; +import org.mapstruct.ap.internal.model.common.Accessibility; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.MappingMethodOptions; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; +import org.mapstruct.ap.internal.util.Strings; + +/** + * This method will be generated in absence of a suitable abstract method to implement. + * + * @author Sjaak Derksen + */ +public class ForgedMethod implements Method { + + private final List parameters; + private final Type returnType; + private final String name; + private final List thrownTypes; + private final ForgedMethodHistory history; + + private final List sourceParameters; + private final List contextParameters; + private final Parameter mappingTargetParameter; + private final MappingReferences mappingReferences; + + private final Method basedOn; + private final boolean forgedNameBased; + private MappingMethodOptions options; + + /** + * Creates a new forged method with the given name for mapping a method parameter to a property. + * + * @param name the (unique name) for this method + * @param sourceType the source type + * @param returnType the return type. + * @param basedOn the method that (originally) triggered this nested method generation. + * @return a new forge method + */ + public static ForgedMethod forParameterMapping(String name, Type sourceType, Type returnType, + Method basedOn) { + return new ForgedMethod( + name, + sourceType, + returnType, + Collections.emptyList(), + basedOn, + null, + MappingReferences.empty(), + false + ); + } + + /** + * Creates a new forged method for mapping a bean property to a property + * + * @param name the (unique name) for this method + * @param sourceType the source type + * @param returnType the return type. + * @param parameters other parameters (including the context + @MappingTarget + * @param basedOn the method that (originally) triggered this nested method generation. + * @param history a parent forged method if this is a forged method within a forged method + * @param mappingReferences the mapping options for this method + * @param forgedNameBased forges a name based (matched) mapping method + * @return a new forge method + */ + public static ForgedMethod forPropertyMapping(String name, Type sourceType, Type returnType, + List parameters, Method basedOn, + ForgedMethodHistory history, MappingReferences mappingReferences, + boolean forgedNameBased) { + return new ForgedMethod( + name, + sourceType, + returnType, + parameters, + basedOn, + history, + mappingReferences == null ? MappingReferences.empty() : mappingReferences, + forgedNameBased + ); + } + + /** + * Creates a new forged method for mapping a collection element, map key/value or stream element + * + * @param name the (unique name) for this method + * @param sourceType the source type + * @param returnType the return type. + * @param basedOn the method that (originally) triggered this nested method generation. + * @param history a parent forged method if this is a forged method within a forged method + * @param forgedNameBased forges a name based (matched) mapping method + * + * @return a new forge method + */ + public static ForgedMethod forElementMapping(String name, Type sourceType, Type returnType, Method basedOn, + ForgedMethodHistory history, boolean forgedNameBased) { + return new ForgedMethod( + name, + sourceType, + returnType, + basedOn.getContextParameters(), + basedOn, + history, + MappingReferences.empty(), + forgedNameBased + ); + } + + /** + * Creates a new forged method for mapping a SubclassMapping element + * + * @param name the (unique name) for this method + * @param sourceType the source type + * @param returnType the return type. + * @param basedOn the method that (originally) triggered this nested method generation. + * @param history a parent forged method if this is a forged method within a forged method + * @param forgedNameBased forges a name based (matched) mapping method + * + * @return a new forge method + */ + public static ForgedMethod forSubclassMapping(String name, Type sourceType, Type returnType, Method basedOn, + MappingReferences mappingReferences, ForgedMethodHistory history, + boolean forgedNameBased) { + return new ForgedMethod( + name, + sourceType, + returnType, + basedOn.getContextParameters(), + basedOn, + history, + mappingReferences == null ? MappingReferences.empty() : mappingReferences, + forgedNameBased, + MappingMethodOptions.getSubclassForgedMethodInheritedOptions( basedOn.getOptions() ) + ); + } + + private ForgedMethod(String name, Type sourceType, Type returnType, List additionalParameters, + Method basedOn, ForgedMethodHistory history, MappingReferences mappingReferences, + boolean forgedNameBased) { + this( + name, + sourceType, + returnType, + additionalParameters, + basedOn, + history, + mappingReferences, + forgedNameBased, + MappingMethodOptions.getForgedMethodInheritedOptions( basedOn.getOptions() ) + ); + } + + private ForgedMethod(String name, Type sourceType, Type returnType, List additionalParameters, + Method basedOn, ForgedMethodHistory history, MappingReferences mappingReferences, + boolean forgedNameBased, MappingMethodOptions options) { + + // establish name + String sourceParamSafeName; + if ( additionalParameters.isEmpty() ) { + sourceParamSafeName = Strings.getSafeVariableName( sourceType.getName() ); + } + else { + sourceParamSafeName = Strings.getSafeVariableName( + sourceType.getName(), + additionalParameters.stream().map( Parameter::getName ).collect( Collectors.toList() ) + ); + } + + // establish parameters + this.parameters = new ArrayList<>( 1 + additionalParameters.size() ); + Parameter sourceParameter = new Parameter( sourceParamSafeName, sourceType ); + this.parameters.add( sourceParameter ); + this.parameters.addAll( additionalParameters ); + this.sourceParameters = Parameter.getSourceParameters( parameters ); + this.contextParameters = Parameter.getContextParameters( parameters ); + this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); + this.returnType = returnType; + this.thrownTypes = new ArrayList<>(); + + // based on method + this.basedOn = basedOn; + + this.name = Strings.sanitizeIdentifierName( name ); + this.history = history; + this.mappingReferences = mappingReferences; + this.forgedNameBased = forgedNameBased; + + this.options = options; + } + + /** + * creates a new ForgedMethod with the same arguments but with a new name + * @param name the new name + * @param forgedMethod existing forge method + */ + public ForgedMethod(String name, ForgedMethod forgedMethod) { + this.parameters = forgedMethod.parameters; + this.returnType = forgedMethod.returnType; + this.thrownTypes = forgedMethod.thrownTypes; + this.history = forgedMethod.history; + + this.sourceParameters = Parameter.getSourceParameters( parameters ); + this.contextParameters = Parameter.getContextParameters( parameters ); + this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); + this.mappingReferences = forgedMethod.mappingReferences; + + this.basedOn = forgedMethod.basedOn; + + this.name = name; + this.forgedNameBased = forgedMethod.forgedNameBased; + + this.options = MappingMethodOptions.getForgedMethodInheritedOptions( basedOn.getOptions() ); + } + + @Override + public boolean matches(List sourceTypes, Type targetType) { + + if ( !targetType.equals( returnType ) ) { + return false; + } + + if ( parameters.size() != sourceTypes.size() ) { + return false; + } + + Iterator srcTypeIt = sourceTypes.iterator(); + Iterator paramIt = parameters.iterator(); + + while ( srcTypeIt.hasNext() && paramIt.hasNext() ) { + Type sourceType = srcTypeIt.next(); + Parameter param = paramIt.next(); + if ( !sourceType.equals( param.getType() ) ) { + return false; + } + } + + return true; + } + + @Override + public Type getDeclaringMapper() { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public List getParameters() { + return parameters; + } + + @Override + public List getSourceParameters() { + return sourceParameters; + } + + @Override + public List getContextParameters() { + return contextParameters; + } + + @Override + public ParameterProvidedMethods getContextProvidedMethods() { + return basedOn.getContextProvidedMethods(); + } + + @Override + public Parameter getMappingTargetParameter() { + return mappingTargetParameter; + } + + @Override + public Parameter getTargetTypeParameter() { + return null; + } + + @Override + public Accessibility getAccessibility() { + return Accessibility.PROTECTED; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public List getThrownTypes() { + return thrownTypes; + } + + public ForgedMethodHistory getHistory() { + return history; + } + + public boolean isForgedNamedBased() { + return forgedNameBased; + } + + public void addThrownTypes(List thrownTypesToAdd) { + for ( Type thrownType : thrownTypesToAdd ) { + // make sure there are no duplicates coming from the keyAssignment thrown types. + if ( !thrownTypes.contains( thrownType ) ) { + thrownTypes.add( thrownType ); + } + } + } + + @Override + public Type getResultType() { + return mappingTargetParameter != null ? mappingTargetParameter.getType() : returnType; + } + + @Override + public List getParameterNames() { + List parameterNames = new ArrayList<>(); + for ( Parameter parameter : getParameters() ) { + parameterNames.add( parameter.getName() ); + } + return parameterNames; + } + + @Override + public boolean overridesMethod() { + return false; + } + + @Override + public ExecutableElement getExecutable() { + return basedOn.getExecutable(); + } + + @Override + public boolean isLifecycleCallbackMethod() { + return false; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder( returnType.toString() ); + sb.append( " " ); + + sb.append( getName() ).append( "(" ).append( Strings.join( parameters, ", " ) ).append( ")" ); + + return sb.toString(); + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public boolean isDefault() { + return false; + } + + @Override + public Type getDefiningType() { + return null; + } + + @Override + public boolean isUpdateMethod() { + return getMappingTargetParameter() != null; + } + + /** + * object factory mechanism not supported for forged methods + * + * @return false + */ + @Override + public boolean isObjectFactory() { + return false; + } + + @Override + public MappingMethodOptions getOptions() { + return options; + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + @Override + public String describe() { + // the name of the forged method is never fully qualified, so no need to distinguish + // between verbose or not. The type knows whether it should log verbose + return getResultType().describe() + ":" + getName() + "(" + getMappingSourceType().describe() + ")"; + } + + public MappingReferences getMappingReferences() { + return mappingReferences; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + ForgedMethod that = (ForgedMethod) o; + + if ( !Objects.equals( parameters, that.parameters ) ) { + return false; + } + return Objects.equals( returnType, that.returnType ); + + } + + @Override + public int hashCode() { + int result = parameters != null ? parameters.hashCode() : 0; + result = 31 * result + ( returnType != null ? returnType.hashCode() : 0 ); + return result; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethodHistory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethodHistory.java new file mode 100644 index 0000000000..3463413705 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethodHistory.java @@ -0,0 +1,118 @@ +/* + * 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.model; + +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + +/** + * Keeps the context where the ForgedMethod is generated, especially handy with nested forged methods + * + * @author Dmytro Polovinkin + */ +public class ForgedMethodHistory { + + private final ForgedMethodHistory prevHistory; + private final String sourceElement; + private final String targetPropertyName; + private final Type targetType; + private final Type sourceType; + private final boolean usePropertyNames; + private String elementType; + + public ForgedMethodHistory(ForgedMethodHistory history, String sourceElement, String targetPropertyName, + Type sourceType, Type targetType, boolean usePropertyNames, String elementType) { + prevHistory = history; + this.sourceElement = sourceElement; + this.targetPropertyName = targetPropertyName; + this.sourceType = sourceType; + this.targetType = targetType; + this.usePropertyNames = usePropertyNames; + this.elementType = elementType; + } + + public Type getTargetType() { + return targetType; + } + + public Type getSourceType() { + return sourceType; + } + + public String createSourcePropertyErrorMessage() { + return conditionallyCapitalizedElementType() + " \"" + getSourceType().describe() + " " + + stripBrackets( getDottedSourceElement() ) + "\""; + } + + /** + * Capitalization mostly matters to avoid the funny "Can't map map key" message. However it's irrelevant for the + * "Can't map property" message. + * + * @return capitalized or non-capitalized element type + */ + private String conditionallyCapitalizedElementType() { + if ( "property".equals( elementType ) ) { + return elementType; + } + else { + return Strings.capitalize( elementType ); + } + } + + public String createTargetPropertyName() { + return stripBrackets( getDottedTargetPropertyName() ); + } + + private String getDottedSourceElement() { + if ( prevHistory == null ) { + return sourceElement; + } + else { + if ( usePropertyNames ) { + return getCorrectDottedPath( prevHistory.getDottedSourceElement(), sourceElement ); + } + else { + return prevHistory.getDottedSourceElement(); + } + } + } + + private String getDottedTargetPropertyName() { + if ( prevHistory == null ) { + return targetPropertyName; + } + else { + if ( usePropertyNames ) { + return getCorrectDottedPath( prevHistory.getDottedTargetPropertyName(), targetPropertyName ); + } + else { + return prevHistory.getDottedTargetPropertyName(); + } + + } + } + + private String getCorrectDottedPath(String previousPath, String currentProperty) { + if ( "map key".equals( elementType ) ) { + return stripBrackets( previousPath ) + "{:key}"; + } + else if ( "map value".equals( elementType ) ) { + return stripBrackets( previousPath ) + "{:value}"; + } + else { + return previousPath + "." + currentProperty; + } + } + + private String stripBrackets(String dottedName) { + if ( dottedName.endsWith( "[]" ) || dottedName.endsWith( "{}" ) ) { + dottedName = dottedName.substring( 0, dottedName.length() - 2 ); + } + return dottedName; + } +} + + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.java new file mode 100644 index 0000000000..e1c320e16b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.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.ap.internal.model; + +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.assignment.OptionalGetWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * An inline conversion from an optional source to it's value. + * + * @author Filip Hrisafov + */ +public class FromOptionalTypeConversion extends ModelElement implements Assignment { + + private final Assignment conversionAssignment; + private final Type optionalType; + + public FromOptionalTypeConversion(Type optionalType, Assignment conversionAssignment) { + this.conversionAssignment = conversionAssignment; + this.optionalType = optionalType; + } + + @Override + public Set getImportTypes() { + return conversionAssignment.getImportTypes(); + } + + @Override + public List getThrownTypes() { + return conversionAssignment.getThrownTypes(); + } + + public Assignment getAssignment() { + return conversionAssignment; + } + + @Override + public String getSourceReference() { + return conversionAssignment.getSourceReference(); + } + + @Override + public boolean isSourceReferenceParameter() { + return conversionAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return conversionAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return conversionAssignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName) { + return conversionAssignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return conversionAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + conversionAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return conversionAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + conversionAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return conversionAssignment.getSourceParameterName(); + } + + @Override + public void setAssignment(Assignment assignment) { + this.conversionAssignment.setAssignment( new OptionalGetWrapper( assignment, optionalType ) ); + } + + @Override + public AssignmentType getType() { + return conversionAssignment.getType(); + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return conversionAssignment.toString(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java index bebde7fa8c..6eb8bbfef8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedType.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model; @@ -23,14 +10,16 @@ import java.util.List; import java.util.SortedSet; import java.util.TreeSet; +import javax.lang.model.type.TypeKind; -import javax.annotation.Generated; - +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.version.VersionInformation; /** @@ -42,55 +31,113 @@ public abstract class GeneratedType extends ModelElement { private static final String JAVA_LANG_PACKAGE = "java.lang"; + protected abstract static class GeneratedTypeBuilder> { + + private final T myself; + protected TypeFactory typeFactory; + protected ElementUtils elementUtils; + protected Options options; + protected VersionInformation versionInformation; + protected SortedSet extraImportedTypes; + + protected List methods; + protected ClassAccessibilityGem classAccessibility; + + GeneratedTypeBuilder(Class selfType) { + myself = selfType.cast( this ); + } + + public T elementUtils(ElementUtils elementUtils) { + this.elementUtils = elementUtils; + return myself; + } + + public T typeFactory(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + return myself; + } + + public T options(Options options) { + this.options = options; + return myself; + } + + public T versionInformation(VersionInformation versionInformation) { + this.versionInformation = versionInformation; + return myself; + } + + public T extraImports(SortedSet extraImportedTypes) { + this.extraImportedTypes = extraImportedTypes; + return myself; + } + + public T methods(List methods) { + this.methods = methods; + return myself; + } + + public T classAccessibility(ClassAccessibilityGem classAccessibility) { + this.classAccessibility = classAccessibility; + return myself; + } + } + private final String packageName; private final String name; - private final String superClassName; - private final String interfacePackage; - private final String interfaceName; + private final Type mapperDefinitionType; private final List annotations; - private final List methods; + private final List methods; private final SortedSet extraImportedTypes; private final boolean suppressGeneratorTimestamp; private final boolean suppressGeneratorVersionComment; private final VersionInformation versionInformation; private final Accessibility accessibility; - private List fields; + private List fields; private Constructor constructor; /** * Type representing the {@code @Generated} annotation */ private final Type generatedType; + private final boolean generatedTypeAvailable; // CHECKSTYLE:OFF - protected GeneratedType(TypeFactory typeFactory, String packageName, String name, String superClassName, - String interfacePackage, String interfaceName, - List methods, - List fields, - Options options, - VersionInformation versionInformation, - Accessibility accessibility, - SortedSet extraImportedTypes, - Constructor constructor ) { + protected GeneratedType(TypeFactory typeFactory, String packageName, String name, + Type mapperDefinitionType, List methods, + List fields, Options options, VersionInformation versionInformation, + boolean suppressGeneratorTimestamp, + Accessibility accessibility, SortedSet extraImportedTypes, Constructor constructor) { this.packageName = packageName; this.name = name; - this.superClassName = superClassName; - this.interfacePackage = interfacePackage; - this.interfaceName = interfaceName; + this.mapperDefinitionType = mapperDefinitionType; this.extraImportedTypes = extraImportedTypes; - this.annotations = new ArrayList(); - this.methods = methods; + this.annotations = new ArrayList<>(); + this.methods = new ArrayList<>(methods); this.fields = fields; - this.suppressGeneratorTimestamp = options.isSuppressGeneratorTimestamp(); + this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorVersionComment = options.isSuppressGeneratorVersionComment(); this.versionInformation = versionInformation; this.accessibility = accessibility; - this.generatedType = typeFactory.getType( Generated.class ); + if ( versionInformation.isSourceVersionAtLeast9() && + typeFactory.isTypeAvailable( "javax.annotation.processing.Generated" ) ) { + this.generatedType = typeFactory.getType( "javax.annotation.processing.Generated" ); + this.generatedTypeAvailable = true; + } + else if ( typeFactory.isTypeAvailable( "javax.annotation.Generated" ) ) { + this.generatedType = typeFactory.getType( "javax.annotation.Generated" ); + this.generatedTypeAvailable = true; + } + else { + this.generatedType = null; + this.generatedTypeAvailable = false; + } + this.constructor = constructor; } @@ -100,20 +147,16 @@ public String getPackageName() { return packageName; } - public String getName() { - return name; + public boolean hasPackageName() { + return !Strings.isEmpty( packageName ); } - public String getSuperClassName() { - return superClassName; - } - - public String getInterfacePackage() { - return interfacePackage; + public String getName() { + return name; } - public String getInterfaceName() { - return interfaceName; + public Type getMapperDefinitionType() { + return mapperDefinitionType; } public List getAnnotations() { @@ -124,15 +167,15 @@ public void addAnnotation(Annotation annotation) { annotations.add( annotation ); } - public List getMethods() { + public List getMethods() { return methods; } - public List getFields() { + public List getFields() { return fields; } - public void setFields(List fields) { + public void setFields(List fields) { this.fields = fields; } @@ -144,6 +187,10 @@ public boolean isSuppressGeneratorVersionComment() { return suppressGeneratorVersionComment; } + public boolean isGeneratedTypeAvailable() { + return generatedTypeAvailable; + } + public VersionInformation getVersionInformation() { return versionInformation; } @@ -152,36 +199,58 @@ public Accessibility getAccessibility() { return accessibility; } + public void setConstructor(Constructor constructor) { + this.constructor = constructor; + } + @Override public SortedSet getImportTypes() { - SortedSet importedTypes = new TreeSet(); - importedTypes.add( generatedType ); + SortedSet importedTypes = new TreeSet<>(); + addIfImportRequired( importedTypes, generatedType ); + + addIfImportRequired( importedTypes, mapperDefinitionType ); - for ( MappingMethod mappingMethod : methods ) { + for ( GeneratedTypeMethod mappingMethod : methods ) { for ( Type type : mappingMethod.getImportTypes() ) { - addWithDependents( importedTypes, type ); + addIfImportRequired( importedTypes, type ); } } for ( Field field : fields ) { if ( field.isTypeRequiresImport() ) { for ( Type type : field.getImportTypes() ) { - addWithDependents( importedTypes, type ); + addIfImportRequired( importedTypes, type ); } } } for ( Annotation annotation : annotations ) { - addWithDependents( importedTypes, annotation.getType() ); + for ( Type type : annotation.getImportTypes() ) { + addIfImportRequired( importedTypes, type ); + } } for ( Type extraImport : extraImportedTypes ) { - addWithDependents( importedTypes, extraImport ); + addIfImportRequired( importedTypes, extraImport ); + } + + if ( constructor != null ) { + for ( Type type : constructor.getImportTypes() ) { + addIfImportRequired( importedTypes, type ); + } } return importedTypes; } + public SortedSet getImportTypeNames() { + SortedSet importTypeNames = new TreeSet<>(); + for ( Type type : getImportTypes() ) { + importTypeNames.add( type.getImportName() ); + } + return importTypeNames; + } + public Constructor getConstructor() { return constructor; } @@ -190,42 +259,50 @@ public void removeConstructor() { constructor = null; } - protected void addWithDependents(Collection collection, Type typeToAdd) { + public Javadoc getJavadoc() { + return null; + } + + protected void addIfImportRequired(Collection collection, Type typeToAdd) { if ( typeToAdd == null ) { return; } - if ( needsImportDeclaration( typeToAdd ) ) { - if ( typeToAdd.isArrayType() ) { - collection.add( typeToAdd.getComponentType() ); + for ( Type type : typeToAdd.getImportTypes() ) { + if ( needsImportDeclaration( type ) ) { + collection.add( type ); } - else { - collection.add( typeToAdd ); - } - } - - for ( Type type : typeToAdd.getTypeParameters() ) { - addWithDependents( collection, type ); } } private boolean needsImportDeclaration(Type typeToAdd) { - if ( !typeToAdd.isImported() ) { + if ( !typeToAdd.isToBeImported() ) { return false; } - if ( typeToAdd.getPackageName() == null ) { + if ( typeToAdd.getTypeMirror().getKind() != TypeKind.DECLARED && !typeToAdd.isArrayType() ) { return false; } - if ( typeToAdd.getPackageName().startsWith( JAVA_LANG_PACKAGE ) ) { - return false; - } - - if ( typeToAdd.getPackageName().equals( packageName ) ) { - if ( !typeToAdd.getTypeElement().getNestingKind().isNested() ) { + if ( typeToAdd.getPackageName() != null ) { + if ( typeToAdd.getPackageName().equals( JAVA_LANG_PACKAGE ) ) { + // only the types in the java.lang package are implicitly imported, the packages under java.lang + // like java.lang.management are not. return false; } + + if ( typeToAdd.getPackageName().equals( packageName ) ) { + if ( typeToAdd.getTypeElement() != null ) { + if ( !typeToAdd.getTypeElement().getNestingKind().isNested() ) { + return false; + } + } + else if ( typeToAdd.getComponentType() != null ) { + if ( !typeToAdd.getComponentType().getTypeElement().getNestingKind().isNested() ) { + return false; + } + } + } } return true; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.java new file mode 100644 index 0000000000..8d6a999665 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/GeneratedTypeMethod.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.ap.internal.model; + +import org.mapstruct.ap.internal.model.common.ModelElement; + +/** + * Base class for methods available in a generated type. + * + * @author Filip Hrisafov + */ +public abstract class GeneratedTypeMethod extends ModelElement { + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java new file mode 100644 index 0000000000..6ff85c585f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java @@ -0,0 +1,251 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import javax.lang.model.element.ExecutableElement; + +import org.mapstruct.ap.internal.model.common.Accessibility; +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; +import org.mapstruct.ap.internal.util.Strings; + +/** + * A non mapping method to be generated. + * + * Can be called from for instance conversions or built-in methods as shared helper method. + * + * One example of such method is the creation of a decimal formatter + * {@link org.mapstruct.ap.internal.conversion.CreateDecimalFormat}, which is used in 2 conversions + * (BigInteger to String and BigDecimal to String) + * + * @author Sjaak Derksen + */ +public abstract class HelperMethod implements Method { + /** + * {@inheritDoc } + * + * @return default method name is equal to class name of build in method name + */ + @Override + public String getName() { + return Strings.decapitalize( this.getClass().getSimpleName() ); + } + + /** + * Returns the types used by this method for which import statements need to be generated. Defaults to the empty + * set. To be overridden by implementations in case they make use of additional types (note that the parameter and + * return type don't need to be added). + * + * @return the types used by this method for which import statements need to be generated + */ + public Set getImportTypes() { + return Collections.emptySet(); + } + + /** + * {@inheritDoc} + *

    + * Default the targetType should be assignable to the returnType and the sourceType to the parameter, + * excluding generic type variables. When the implementor sees a need for this, this method can be overridden. + */ + @Override + public boolean matches(List sourceTypes, Type targetType) { + throw new IllegalStateException( "Irrelevant." ); + + } + + @Override + public List getSourceParameters() { + return getParameters(); + } + + @Override + public List getContextParameters() { + return Collections.emptyList(); + } + + @Override + public ParameterProvidedMethods getContextProvidedMethods() { + return ParameterProvidedMethods.empty(); + } + + /** + * {@inheritDoc} + *

    + * For built-in methods, the declaring mapper is always {@code null} as they will be added as private methods to the + * generated mapper. + * + * @return {@code null} + */ + @Override + public final Type getDeclaringMapper() { + return null; + } + + @Override + public List getParameters() { + return Arrays.asList( getParameter() ); + } + + /** + * mapping target parameter mechanism not supported for built-in methods + * + * @return {@code null} + */ + @Override + public Parameter getMappingTargetParameter() { + return null; + } + + /** + * target type parameter mechanism not supported for built-in methods + * + * @return {@code null} + */ + @Override + public Parameter getTargetTypeParameter() { + return null; + } + + /** + * object factory mechanism not supported for built-in methods + * + * @return false + */ + @Override + public boolean isObjectFactory() { + return false; + } + + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + + /** + * the conversion context is used to format an auxiliary parameter in the method call with context specific + * information such as a date format. + * + * @param conversionContext context + * @return null if no context parameter should be included "null" if there should be an explicit null call + * "'dateFormat'" for instance, to indicate how the build-in method should format the date + */ + public String getContextParameter(ConversionContext conversionContext) { + return null; + } + + /** + * hashCode based on class + * + * @return hashCode + */ + @Override + public int hashCode() { + return this.getClass().hashCode(); + } + + /** + * equals based on class + * + * @param obj other class + * + * @return true when classes are the same + */ + @Override + public boolean equals(Object obj) { + if ( obj == null ) { + return false; + } + return ( getClass() == obj.getClass() ); + } + + /** + * Analyzes the Java Generic type variables in the parameter do match the type variables in the build in method same + * goes for the returnType. + * + * @param parameter source + * @param returnType target + * @return {@code true}, iff the type variables match + */ + public boolean doTypeVarsMatch(Type parameter, Type returnType) { + return true; + } + + /** + * There's currently only one parameter foreseen instead of a list of parameter + * + * @return the parameter + */ + public abstract Parameter getParameter(); + + @Override + public Accessibility getAccessibility() { + return Accessibility.PRIVATE; + } + + @Override + public List getThrownTypes() { + return Collections.emptyList(); + } + + @Override + public Type getResultType() { + return getReturnType(); + } + + @Override + public List getParameterNames() { + List parameterNames = new ArrayList<>( getParameters().size() ); + for ( Parameter parameter : getParameters() ) { + parameterNames.add( parameter.getName() ); + } + + return parameterNames; + } + + @Override + public boolean overridesMethod() { + return false; + } + + @Override + public ExecutableElement getExecutable() { + return null; + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public boolean isDefault() { + return false; + } + + @Override + public Type getDefiningType() { + return null; + } + + @Override + public boolean isLifecycleCallbackMethod() { + return false; + } + + @Override + public boolean isUpdateMethod() { + return false; // irrelevant + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableCreation.java b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableCreation.java new file mode 100644 index 0000000000..cf7bc6e2eb --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableCreation.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.ap.internal.model; + +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NewInstanceCreation; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * Model element that can be used to create a type of {@link Iterable} or {@link java.util.Map}. If an implementation + * type is used and the target type has a constructor with {@code int} as parameter and the source parameter is of + * {@link java.util.Collection}, {@link java.util.Map} or {@code Array} type then MapStruct will use that constructor + * with the {@code size} / {@code length} from the source parameter. + * + * @author Filip Hrisafov + */ +public class IterableCreation extends ModelElement { + + private final Type resultType; + private final Parameter sourceParameter; + private final MethodReference factoryMethod; + private final NewInstanceCreation newInstance; + private final boolean canUseSize; + private final boolean loadFactorAdjustment; + + private IterableCreation(Type resultType, Parameter sourceParameter, MethodReference factoryMethod) { + this.resultType = resultType; + this.sourceParameter = sourceParameter; + this.factoryMethod = factoryMethod; + this.newInstance = factoryMethod == null ? NewInstanceCreation.forType( resultType ) : null; + this.canUseSize = ( sourceParameter.getType().isCollectionOrMapType() || + sourceParameter.getType().isArrayType() ) + && resultType.getImplementation() != null && resultType.getImplementation().hasInitialCapacityConstructor(); + this.loadFactorAdjustment = this.canUseSize && resultType.getImplementation().isLoadFactorAdjustment(); + + } + + public static IterableCreation create(NormalTypeMappingMethod mappingMethod, Parameter sourceParameter) { + return new IterableCreation( mappingMethod.getResultType(), sourceParameter, mappingMethod.getFactoryMethod() ); + } + + public Type getResultType() { + return resultType; + } + + public Parameter getSourceParameter() { + return sourceParameter; + } + + public MethodReference getFactoryMethod() { + return this.factoryMethod; + } + + public NewInstanceCreation getNewInstance() { + return newInstance; + } + + public boolean isCanUseSize() { + return canUseSize; + } + + public boolean isLoadFactorAdjustment() { + return loadFactorAdjustment; + } + + @Override + public Set getImportTypes() { + Set types = new HashSet<>(); + if ( newInstance != null ) { + types.addAll( newInstance.getImportTypes() ); + } + + if ( isEnumSet() ) { + types.add( getEnumSetElementType() ); + // The result type itself is an EnumSet + types.add( resultType ); + } + return types; + } + + public Type getEnumSetElementType() { + return first( getResultType().determineTypeArguments( Iterable.class ) ); + } + + public boolean isEnumSet() { + return "java.util.EnumSet".equals( resultType.getFullyQualifiedName() ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java index e0cfb8acb9..220466f802 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java @@ -1,41 +1,23 @@ -/** - * Copyright 2012-2015 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.model; -import static org.mapstruct.ap.internal.util.Collections.first; - +import java.util.Collection; import java.util.List; import java.util.Set; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.model.assignment.Assignment; import org.mapstruct.ap.internal.model.assignment.LocalVarWrapper; import org.mapstruct.ap.internal.model.assignment.SetterWrapper; -import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; -import org.mapstruct.ap.internal.util.Message; -import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.model.source.SelectionParameters; + +import static org.mapstruct.ap.internal.util.Collections.first; /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one iterable type to another. The collection @@ -43,207 +25,83 @@ * * @author Gunnar Morling */ -public class IterableMappingMethod extends MappingMethod { - - private final Assignment elementAssignment; - private final MethodReference factoryMethod; - private final boolean overridden; - private final boolean mapNullToDefault; - private final String loopVariableName; - - public static class Builder { - - private Method method; - private MappingBuilderContext ctx; - private String dateFormat; - private List qualifiers; - private TypeMirror qualifyingElementTargetType; - private NullValueMappingStrategyPrism nullValueMappingStrategy; +public class IterableMappingMethod extends ContainerMappingMethod { - public Builder mappingContext(MappingBuilderContext mappingContext) { - this.ctx = mappingContext; - return this; - } - - public Builder method(Method sourceMethod) { - this.method = sourceMethod; - return this; - } + public static class Builder extends ContainerMappingMethodBuilder { - public Builder dateFormat(String dateFormat) { - this.dateFormat = dateFormat; - return this; + public Builder() { + super( Builder.class, "collection element" ); } - public Builder qualifiers(List qualifiers) { - this.qualifiers = qualifiers; - return this; + @Override + protected Type getElementType(Type parameterType) { + return parameterType.isArrayType() ? parameterType.getComponentType() : first( + parameterType.determineTypeArguments( Iterable.class ) ).getTypeBound(); } - public Builder qualifyingElementTargetType(TypeMirror qualifyingElementTargetType) { - this.qualifyingElementTargetType = qualifyingElementTargetType; - return this; - } - - public Builder nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) { - this.nullValueMappingStrategy = nullValueMappingStrategy; - return this; - } - - public IterableMappingMethod build() { - - - Type sourceParameterType = first( method.getSourceParameters() ).getType(); + @Override + protected Assignment getWrapper(Assignment assignment, Method method) { Type resultType = method.getResultType(); - - Type sourceElementType = sourceParameterType.isArrayType() ? sourceParameterType.getComponentType() - : first( sourceParameterType.getTypeParameters() ).getTypeBound(); - Type targetElementType = resultType.isArrayType() ? resultType.getComponentType() - : first( resultType.getTypeParameters() ).getTypeBound(); - - String loopVariableName = - Strings.getSaveVariableName( sourceElementType.getName(), method.getParameterNames() ); - - Assignment assignment = ctx.getMappingResolver().getTargetAssignment( - method, - "collection element", - sourceElementType, - targetElementType, - null, // there is no targetPropertyName - dateFormat, - qualifiers, - qualifyingElementTargetType, - loopVariableName, - false - ); - - if ( assignment == null ) { - if ( method instanceof ForgedMethod ) { - // leave messaging to calling property mapping - return null; - } - else { - ctx.getMessager().printMessage( method.getExecutable(), Message.ITERABLEMAPPING_MAPPING_NOT_FOUND ); - } - } - else { - if ( method instanceof ForgedMethod ) { - ForgedMethod forgedMethod = (ForgedMethod) method; - forgedMethod.addThrownTypes( assignment.getThrownTypes() ); - } - } // target accessor is setter, so decorate assignment as setter if ( resultType.isArrayType() ) { - assignment = new LocalVarWrapper( assignment, method.getThrownTypes() ); + return new LocalVarWrapper( assignment, method.getThrownTypes(), resultType, false ); } else { - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + return new SetterWrapper( assignment, method.getThrownTypes(), false ); } - - // mapNullToDefault - boolean mapNullToDefault = false; - if ( method.getMapperConfiguration() != null ) { - mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy ); - } - - MethodReference factoryMethod = null; - if ( !method.isUpdateMethod() ) { - factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null, null ); - } - - List beforeMappingMethods = - LifecycleCallbackFactory.beforeMappingMethods( method, qualifiers, ctx ); - List afterMappingMethods = - LifecycleCallbackFactory.afterMappingMethods( method, qualifiers, ctx ); - - return new IterableMappingMethod( - method, - assignment, - factoryMethod, - mapNullToDefault, - loopVariableName, - beforeMappingMethods, - afterMappingMethods ); } - } - - private IterableMappingMethod(Method method, Assignment parameterAssignment, MethodReference factoryMethod, - boolean mapNullToDefault, String loopVariableName, - List beforeMappingReferences, - List afterMappingReferences) { - super( method, beforeMappingReferences, afterMappingReferences ); - this.elementAssignment = parameterAssignment; - this.factoryMethod = factoryMethod; - this.overridden = method.overridesMethod(); - this.mapNullToDefault = mapNullToDefault; - this.loopVariableName = loopVariableName; - } - - public Parameter getSourceParameter() { - for ( Parameter parameter : getParameters() ) { - if ( !parameter.isMappingTarget() ) { - return parameter; - } + @Override + protected IterableMappingMethod instantiateMappingMethod(Method method, Collection existingVariables, + Assignment assignment, MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, + List beforeMappingMethods, + List afterMappingMethods, SelectionParameters selectionParameters, + PresenceCheck sourceParameterPresenceCheck) { + return new IterableMappingMethod( + method, + getMethodAnnotations(), + existingVariables, + assignment, + factoryMethod, + mapNullToDefault, + loopVariableName, + beforeMappingMethods, + afterMappingMethods, + selectionParameters, + sourceParameterPresenceCheck + ); } - - throw new IllegalStateException( "Method " + this + " has no source parameter." ); } - public Assignment getElementAssignment() { - return elementAssignment; + //CHECKSTYLE:OFF + private IterableMappingMethod(Method method, List annotations, + Collection existingVariables, Assignment parameterAssignment, + MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, + List beforeMappingReferences, + List afterMappingReferences, + SelectionParameters selectionParameters, PresenceCheck sourceParameterPresenceCheck) { + //CHECKSTYLE:ON + super( + method, + annotations, + existingVariables, + parameterAssignment, + factoryMethod, + mapNullToDefault, + loopVariableName, + beforeMappingReferences, + afterMappingReferences, + selectionParameters, + sourceParameterPresenceCheck + ); } @Override public Set getImportTypes() { Set types = super.getImportTypes(); - if ( elementAssignment != null ) { - types.addAll( elementAssignment.getImportTypes() ); - } - if ( ( factoryMethod == null ) && ( !isExistingInstanceMapping() ) ) { - types.addAll( getReturnType().getImportTypes() ); - if ( getReturnType().getImplementationType() != null ) { - types.addAll( getReturnType().getImplementationType().getImportTypes() ); - } - } - return types; - } - - public boolean isMapNullToDefault() { - return mapNullToDefault; - } - public boolean isOverridden() { - return overridden; - } - - public String getLoopVariableName() { - return loopVariableName; - } - - public String getDefaultValue() { - TypeKind kind = getResultElementType().getTypeMirror().getKind(); - switch ( kind ) { - case BOOLEAN: - return "false"; - case BYTE: - case SHORT: - case INT: - case CHAR: /*"'\u0000'" would have been better, but depends on platformencoding */ - return "0"; - case LONG: - return "0L"; - case FLOAT: - return "0.0f"; - case DOUBLE: - return "0.0d"; - default: - return "null"; - } - } - - public MethodReference getFactoryMethod() { - return this.factoryMethod; + types.add( getSourceElementType() ); + return types; } public Type getSourceElementType() { @@ -253,66 +111,17 @@ public Type getSourceElementType() { return sourceParameterType.getComponentType(); } else { - return sourceParameterType.getTypeParameters().get( 0 ).getTypeBound(); + return first( sourceParameterType.determineTypeArguments( Iterable.class ) ).getTypeBound(); } } + @Override public Type getResultElementType() { if ( getResultType().isArrayType() ) { return getResultType().getComponentType(); } else { - return getResultType().getTypeParameters().get( 0 ); + return first( getResultType().determineTypeArguments( Iterable.class ) ); } } - - public String getIndex1Name() { - return Strings.getSaveVariableName( "i", loopVariableName, getSourceParameter().getName(), getResultName() ); - } - - public String getIndex2Name() { - return Strings.getSaveVariableName( "j", loopVariableName, getSourceParameter().getName(), getResultName() ); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ( ( getResultType() == null ) ? 0 : getResultType().hashCode() ); - return result; - } - - @Override - public boolean equals(Object obj) { - if ( this == obj ) { - return true; - } - if ( obj == null ) { - return false; - } - if ( getClass() != obj.getClass() ) { - return false; - } - IterableMappingMethod other = (IterableMappingMethod) obj; - - if ( !getResultType().equals( other.getResultType() ) ) { - return false; - } - - if ( getSourceParameters().size() != other.getSourceParameters().size() ) { - return false; - } - - for ( int i = 0; i < getSourceParameters().size(); i++ ) { - List thisTypeParameters = getSourceParameters().get( i ).getType().getTypeParameters(); - List otherTypeParameters = other.getSourceParameters().get( i ).getType().getTypeParameters(); - - if ( !thisTypeParameters.equals( otherTypeParameters ) ) { - return false; - } - } - - return true; - } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java new file mode 100644 index 0000000000..b8718b68e8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Javadoc.java @@ -0,0 +1,92 @@ +/* + * 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.model; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Represents the javadoc information that should be generated for a {@link Mapper}. + * + * @author Jose Carlos Campanero Ortiz + */ +public class Javadoc extends ModelElement { + + public static class Builder { + + private String value; + private List authors; + private String deprecated; + private String since; + + public Builder value(String value) { + this.value = value; + return this; + } + + public Builder authors(List authors) { + this.authors = authors; + return this; + } + + public Builder deprecated(String deprecated) { + this.deprecated = deprecated; + return this; + } + + public Builder since(String since) { + this.since = since; + return this; + } + + public Javadoc build() { + return new Javadoc( + value, + authors, + deprecated, + since + ); + } + } + + private final String value; + private final List authors; + private final String deprecated; + private final String since; + + private Javadoc(String value, List authors, String deprecated, String since) { + this.value = value; + this.authors = authors != null ? Collections.unmodifiableList( authors ) : Collections.emptyList(); + this.deprecated = deprecated; + this.since = since; + } + + public String getValue() { + return value; + } + + public List getAuthors() { + return authors; + } + + public String getDeprecated() { + return deprecated; + } + + public String getSince() { + return since; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java deleted file mode 100644 index 44b7dc61f6..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java +++ /dev/null @@ -1,257 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.model.common.Parameter; -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.model.source.selector.QualifierSelector; -import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; - -/** - * Factory for creating lists of appropriate {@link LifecycleCallbackMethodReference}s - * - * @author Andreas Gudian - */ -public final class LifecycleCallbackFactory { - - private LifecycleCallbackFactory() { - } - - /** - * @param method the method to obtain the beforeMapping methods for - * @param qualifiers method qualifiers - * @param ctx the builder context - * @return all applicable {@code @BeforeMapping} methods for the given method - */ - public static List beforeMappingMethods( - Method method, List qualifiers, MappingBuilderContext ctx) { - return collectLifecycleCallbackMethods( - method, - qualifiers, - filterBeforeMappingMethods( ctx.getSourceModel() ), - ctx ); - } - - /** - * @param method the method to obtain the afterMapping methods for - * @param qualifiers method qualifiers - * @param ctx the builder context - * @return all applicable {@code @AfterMapping} methods for the given method - */ - public static List afterMappingMethods( - Method method, List qualifiers, MappingBuilderContext ctx) { - return collectLifecycleCallbackMethods( - method, - qualifiers, - filterAfterMappingMethods( ctx.getSourceModel() ), - ctx ); - } - - private static List collectLifecycleCallbackMethods( - Method method, List qualifiers, List callbackMethods, MappingBuilderContext ctx) { - - Map> parameterAssignmentsForSourceMethod = - new HashMap>(); - - List candidates = - filterCandidatesByType( method, callbackMethods, parameterAssignmentsForSourceMethod, ctx ); - - candidates = filterCandidatesByQualifiers( method, qualifiers, candidates, ctx ); - - return toLifecycleCallbackMethodRefs( candidates, parameterAssignmentsForSourceMethod, ctx ); - } - - private static List filterCandidatesByQualifiers(Method method, List qualifiers, - List candidates, - MappingBuilderContext ctx) { - QualifierSelector selector = new QualifierSelector( ctx.getTypeUtils(), ctx.getElementUtils() ); - - return selector.getMatchingMethods( method, candidates, null, null, new SelectionCriteria( - qualifiers, - null, - null, - false ) ); - } - - private static List toLifecycleCallbackMethodRefs( - List candidates, Map> parameterAssignmentsForSourceMethod, - MappingBuilderContext ctx) { - - List result = new ArrayList(); - for ( SourceMethod candidate : candidates ) { - markMapperReferenceAsUsed( ctx.getMapperReferences(), candidate ); - result.add( new LifecycleCallbackMethodReference( - candidate, - parameterAssignmentsForSourceMethod.get( candidate ) ) ); - } - return result; - } - - private static List filterCandidatesByType(Method method, - List callbackMethods, Map> parameterAssignmentsForSourceMethod, - MappingBuilderContext ctx) { - - List candidates = new ArrayList(); - - List availableParams = getAvailableParameters( method, ctx ); - for ( SourceMethod callback : callbackMethods ) { - List parameterAssignments = getParameterAssignments( availableParams, callback.getParameters() ); - - if ( parameterAssignments != null - && callback.matches( extractSourceTypes( parameterAssignments ), method.getResultType() ) ) { - - parameterAssignmentsForSourceMethod.put( callback, parameterAssignments ); - candidates.add( callback ); - } - } - return candidates; - } - - private static List getAvailableParameters(Method method, MappingBuilderContext ctx) { - List availableParams = new ArrayList( method.getParameters() ); - if ( method.getMappingTargetParameter() == null ) { - availableParams.add( new Parameter( null, method.getResultType(), true, false ) ); - } - - Parameter targetTypeParameter = new Parameter( - null, - ctx.getTypeFactory().classTypeOf( method.getResultType() ), - false, - true ); - - availableParams.add( targetTypeParameter ); - return availableParams; - } - - private static void markMapperReferenceAsUsed(List references, Method method) { - for ( MapperReference ref : references ) { - if ( ref.getType().equals( method.getDeclaringMapper() ) ) { - ref.setUsed( !method.isStatic() ); - ref.setTypeRequiresImport( true ); - - return; - } - } - } - - private static List extractSourceTypes(List parameters) { - List result = new ArrayList( parameters.size() ); - - for ( Parameter param : parameters ) { - if ( !param.isMappingTarget() && !param.isTargetType() ) { - result.add( param.getType() ); - } - } - - return result; - } - - private static List getParameterAssignments( - List availableParams, List methodParameters) { - List result = new ArrayList( methodParameters.size() ); - - for ( Parameter methodParam : methodParameters ) { - List assignableParams = findCandidateParameters( availableParams, methodParam ); - - if ( assignableParams.isEmpty() ) { - return null; - } - - if ( assignableParams.size() == 1 ) { - result.add( assignableParams.get( 0 ) ); - } - else if ( assignableParams.size() > 1 ) { - Parameter paramWithMatchingName = findParameterWithName( assignableParams, methodParam.getName() ); - - if ( paramWithMatchingName != null ) { - result.add( paramWithMatchingName ); - } - else { - return null; - } - } - } - - return result; - } - - private static Parameter findParameterWithName(List parameters, String name) { - for ( Parameter param : parameters ) { - if ( name.equals( param.getName() ) ) { - return param; - } - } - - return null; - } - - private static List findCandidateParameters(List candiateParameters, Parameter parameter) { - List result = new ArrayList( candiateParameters.size() ); - - for ( Parameter candidate : candiateParameters ) { - if ( ( isTypeVarOrWildcard( parameter ) || candidate.getType().isAssignableTo( parameter.getType() ) ) - && parameter.isMappingTarget() == candidate.isMappingTarget() - && !parameter.isTargetType() && !candidate.isTargetType() ) { - result.add( candidate ); - } - else if ( parameter.isTargetType() && candidate.isTargetType() ) { - result.add( candidate ); - } - } - - return result; - } - - private static boolean isTypeVarOrWildcard(Parameter parameter) { - TypeKind kind = parameter.getType().getTypeMirror().getKind(); - return kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD; - } - - private static List filterBeforeMappingMethods(List methods) { - List result = new ArrayList(); - for ( SourceMethod method : methods ) { - if ( method.isBeforeMappingMethod() ) { - result.add( method ); - } - } - - return result; - } - - private static List filterAfterMappingMethods(List methods) { - List result = new ArrayList(); - for ( SourceMethod method : methods ) { - if ( method.isAfterMappingMethod() ) { - result.add( method ); - } - } - - return result; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java index 60f43c5d97..c47fd258c0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java @@ -1,30 +1,18 @@ -/** - * Copyright 2012-2015 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.model; -import java.beans.Introspector; -import java.util.List; import java.util.Set; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.Strings; @@ -33,41 +21,101 @@ * * @author Andreas Gudian */ -public class LifecycleCallbackMethodReference extends MappingMethod { +public class LifecycleCallbackMethodReference extends MethodReference { private final Type declaringType; - private final List parameterAssignments; + private final Type methodReturnType; + private final Type methodResultType; + private final String targetVariableName; + + private LifecycleCallbackMethodReference(SelectedMethod lifecycleMethod, + MapperReference mapperReference, Parameter providingParameter, + Method containingMethod, Set existingVariableNames) { + super( + lifecycleMethod.getMethod(), + mapperReference, + providingParameter, + lifecycleMethod.getParameterBindings() ); + + this.declaringType = lifecycleMethod.getMethod().getDeclaringMapper(); + this.methodReturnType = containingMethod.getReturnType(); + this.methodResultType = containingMethod.getResultType(); - public LifecycleCallbackMethodReference(SourceMethod method, List parameterAssignments) { - super( method ); - this.declaringType = method.getDeclaringMapper(); - this.parameterAssignments = parameterAssignments; + if ( hasReturnType() ) { + this.targetVariableName = Strings.getSafeVariableName( "target", existingVariableNames ); + existingVariableNames.add( this.targetVariableName ); + } + else { + this.targetVariableName = null; + } } public Type getDeclaringType() { return declaringType; } - public String getInstanceVariableName() { - return Strings.getSaveVariableName( Introspector.decapitalize( declaringType.getName() ) ); + /** + * Returns the return type of the mapping method in which this callback method is called + * + * @return return type + * @see Method#getReturnType() + */ + public Type getMethodReturnType() { + return methodReturnType; + } + + /** + * Returns the result type of the mapping method in which this callback method is called + * + * @return result type + * @see Method#getResultType() + */ + public Type getMethodResultType() { + return methodResultType; + } + + public String getTargetVariableName() { + return targetVariableName; } @Override public Set getImportTypes() { - return declaringType != null ? Collections.asSet( declaringType ) : java.util.Collections. emptySet(); + return declaringType != null ? Collections.asSet( declaringType ) : java.util.Collections.emptySet(); } - public List getParameterAssignments() { - return parameterAssignments; + public boolean hasMappingTargetParameter() { + return getParameterBindings().stream().anyMatch( ParameterBinding::isMappingTarget ); } - public boolean hasMappingTargetParameter() { - for ( Parameter param : parameterAssignments ) { - if ( param.isMappingTarget() ) { - return true; - } - } + /** + * @return true if this callback method has a return type that is not void + */ + public boolean hasReturnType() { + return !getReturnType().isVoid(); + } + + public static LifecycleCallbackMethodReference forParameterProvidedMethod( + SelectedMethod lifecycleMethod, + Parameter providingParameter, Method containingMethod, + Set existingVariableNames) { + + return new LifecycleCallbackMethodReference( + lifecycleMethod, + null, + providingParameter, + containingMethod, + existingVariableNames ); + } + + public static LifecycleCallbackMethodReference forMethodReference(SelectedMethod lifecycleMethod, + MapperReference mapperReference, Method containingMethod, + Set existingVariableNames) { - return false; + return new LifecycleCallbackMethodReference( + lifecycleMethod, + mapperReference, + null, + containingMethod, + existingVariableNames ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java new file mode 100644 index 0000000000..ada9dac470 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -0,0 +1,220 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; +import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; +import org.mapstruct.ap.internal.model.source.selector.SelectionContext; + +/** + * Factory for creating lists of appropriate {@link LifecycleCallbackMethodReference}s + * + * @author Andreas Gudian + */ +public final class LifecycleMethodResolver { + + private LifecycleMethodResolver() { + } + + /** + * @param method the method to obtain the beforeMapping methods for + * @param alternativeTarget alternative to {@link Method#getResultType()} e.g. when target is abstract + * @param selectionParameters method selectionParameters + * @param ctx the builder context + * @param existingVariableNames the existing variable names in the mapping method + * @return all applicable {@code @BeforeMapping} methods for the given method + */ + public static List beforeMappingMethods(Method method, + Type alternativeTarget, + SelectionParameters selectionParameters, + MappingBuilderContext ctx, + Set existingVariableNames) { + return collectLifecycleCallbackMethods( method, + alternativeTarget, + selectionParameters, + filterBeforeMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), + ctx, + existingVariableNames ); + } + + /** + * @param method the method to obtain the afterMapping methods for + * @param alternativeTarget alternative to {@link Method#getResultType()} e.g. when target is abstract + * @param selectionParameters method selectionParameters + * @param ctx the builder context + * @param existingVariableNames list of already used variable names + * @return all applicable {@code @AfterMapping} methods for the given method + */ + public static List afterMappingMethods(Method method, + Type alternativeTarget, + SelectionParameters selectionParameters, + MappingBuilderContext ctx, + Set existingVariableNames, + Supplier> parameterBindingsProvider) { + return collectLifecycleCallbackMethods( method, + alternativeTarget, + selectionParameters, + filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), + ctx, + existingVariableNames, + parameterBindingsProvider + ); + } + + /** + * @param method the method to obtain the beforeMapping methods for + * @param selectionParameters method selectionParameters + * @param ctx the builder context + * @param existingVariableNames the existing variable names in the mapping method + * @return all applicable {@code @BeforeMapping} methods for the given method + */ + public static List beforeMappingMethods(Method method, + SelectionParameters selectionParameters, + MappingBuilderContext ctx, + Set existingVariableNames) { + return collectLifecycleCallbackMethods( method, + method.getResultType(), + selectionParameters, + filterBeforeMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), + ctx, + existingVariableNames ); + } + + /** + * @param method the method to obtain the afterMapping methods for + * @param selectionParameters method selectionParameters + * @param ctx the builder context + * @param existingVariableNames list of already used variable names + * @return all applicable {@code @AfterMapping} methods for the given method + */ + public static List afterMappingMethods(Method method, + SelectionParameters selectionParameters, + MappingBuilderContext ctx, + Set existingVariableNames) { + return collectLifecycleCallbackMethods( method, + method.getResultType(), + selectionParameters, + filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), + ctx, + existingVariableNames ); + } + + private static List getAllAvailableMethods(Method method, List sourceModelMethods) { + ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods(); + if ( contextProvidedMethods.isEmpty() ) { + return sourceModelMethods; + } + + List methodsProvidedByParams = contextProvidedMethods + .getAllProvidedMethodsInParameterOrder( method.getContextParameters() ); + + List availableMethods = + new ArrayList<>( methodsProvidedByParams.size() + sourceModelMethods.size() ); + + availableMethods.addAll( methodsProvidedByParams ); + availableMethods.addAll( sourceModelMethods ); + + return availableMethods; + } + + private static List collectLifecycleCallbackMethods( + Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, + MappingBuilderContext ctx, Set existingVariableNames) { + return collectLifecycleCallbackMethods( + method, + targetType, + selectionParameters, + callbackMethods, + ctx, + existingVariableNames, + Collections::emptyList + ); + } + + private static List collectLifecycleCallbackMethods( + Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, + MappingBuilderContext ctx, Set existingVariableNames, + Supplier> parameterBindingsProvider) { + + MethodSelectors selectors = + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() ); + + List> matchingMethods = selectors.getMatchingMethods( + callbackMethods, + SelectionContext.forLifecycleMethods( + method, + targetType, + selectionParameters, + ctx.getTypeFactory(), + parameterBindingsProvider + ) + ); + + return toLifecycleCallbackMethodRefs( + method, + matchingMethods, + ctx, + existingVariableNames ); + } + + private static List toLifecycleCallbackMethodRefs(Method method, + List> candidates, + MappingBuilderContext ctx, + Set existingVariableNames) { + + List result = new ArrayList<>(); + for ( SelectedMethod candidate : candidates ) { + Parameter providingParameter = + method.getContextProvidedMethods().getParameterForProvidedMethod( candidate.getMethod() ); + + if ( providingParameter != null ) { + result.add( LifecycleCallbackMethodReference.forParameterProvidedMethod( + candidate, + providingParameter, + method, + existingVariableNames ) ); + } + else { + MapperReference mapperReference = MapperReference.findMapperReference( + ctx.getMapperReferences(), + candidate.getMethod() ); + + result.add( LifecycleCallbackMethodReference.forMethodReference( + candidate, + mapperReference, + method, + existingVariableNames ) ); + } + } + return result; + } + + private static List filterBeforeMappingMethods(List methods) { + return methods.stream() + .filter( SourceMethod::isBeforeMappingMethod ) + .collect( Collectors.toList() ); + } + + private static List filterAfterMappingMethods(List methods) { + return methods.stream() + .filter( SourceMethod::isAfterMappingMethod ) + .collect( Collectors.toList() ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java index 978d99db88..5a49e3a726 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java @@ -1,133 +1,104 @@ -/** - * Copyright 2012-2015 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.model; -import static org.mapstruct.ap.internal.util.Collections.first; - +import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.model.assignment.Assignment; import org.mapstruct.ap.internal.model.assignment.LocalVarWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; +import static org.mapstruct.ap.internal.util.Collections.first; + /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one {@code Map} type to another. Keys and * values are mapped either by a {@link TypeConversion} or another mapping method if required. * * @author Gunnar Morling */ -public class MapMappingMethod extends MappingMethod { +public class MapMappingMethod extends NormalTypeMappingMethod { private final Assignment keyAssignment; private final Assignment valueAssignment; - private final MethodReference factoryMethod; - private final boolean overridden; - private final boolean mapNullToDefault; - - public static class Builder { - - private String keyDateFormat; - private String valueDateFormat; - private List keyQualifiers; - private List valueQualifiers; - private TypeMirror keyQualifyingTargetType; - private TypeMirror valueQualifyingTargetType; - private Method method; - private MappingBuilderContext ctx; - private NullValueMappingStrategyPrism nullValueMappingStrategy; - - public Builder mappingContext(MappingBuilderContext mappingContext) { - this.ctx = mappingContext; - return this; - } + private final Parameter sourceParameter; + private final PresenceCheck sourceParameterPresenceCheck; + private IterableCreation iterableCreation; - public Builder method(Method sourceMethod) { - this.method = sourceMethod; - return this; - } + public static class Builder extends AbstractMappingMethodBuilder { - public Builder keyDateFormat(String keyDateFormat) { - this.keyDateFormat = keyDateFormat; - return this; - } + private FormattingParameters keyFormattingParameters; + private FormattingParameters valueFormattingParameters; + private SelectionParameters keySelectionParameters; + private SelectionParameters valueSelectionParameters; - public Builder valueDateFormat(String valueDateFormat) { - this.valueDateFormat = valueDateFormat; - return this; + public Builder() { + super( Builder.class ); } - public Builder keyQualifiers(List keyQualifiers) { - this.keyQualifiers = keyQualifiers; + public Builder keySelectionParameters(SelectionParameters keySelectionParameters) { + this.keySelectionParameters = keySelectionParameters; return this; } - public Builder valueQualifiers(List valueQualifiers) { - this.valueQualifiers = valueQualifiers; + public Builder valueSelectionParameters(SelectionParameters valueSelectionParameters) { + this.valueSelectionParameters = valueSelectionParameters; return this; } - public Builder keyQualifyingTargetType(TypeMirror keyQualifyingTargetType) { - this.keyQualifyingTargetType = keyQualifyingTargetType; + public Builder keyFormattingParameters(FormattingParameters keyFormattingParameters) { + this.keyFormattingParameters = keyFormattingParameters; return this; } - public Builder valueQualifyingTargetType(TypeMirror valueQualifyingTargetType) { - this.valueQualifyingTargetType = valueQualifyingTargetType; + public Builder valueFormattingParameters(FormattingParameters valueFormattingParameters) { + this.valueFormattingParameters = valueFormattingParameters; return this; } - public Builder nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) { - this.nullValueMappingStrategy = nullValueMappingStrategy; - return this; - } - - - public MapMappingMethod build() { - List sourceTypeParams = first( method.getSourceParameters() ).getType().getTypeParameters(); - List resultTypeParams = method.getResultType().getTypeParameters(); + List sourceTypeParams = + first( method.getSourceParameters() ).getType().determineTypeArguments( Map.class ); + List resultTypeParams = method.getResultType().determineTypeArguments( Map.class ); // find mapping method or conversion for key Type keySourceType = sourceTypeParams.get( 0 ).getTypeBound(); Type keyTargetType = resultTypeParams.get( 0 ).getTypeBound(); + SourceRHS keySourceRHS = new SourceRHS( "entry.getKey()", keySourceType, new HashSet<>(), "map key" ); + + SelectionCriteria keyCriteria = SelectionCriteria.forMappingMethods( + keySelectionParameters, + method.getOptions().getMapMapping().getKeyMappingControl( ctx.getElementUtils() ), + null, + false + ); + Assignment keyAssignment = ctx.getMappingResolver().getTargetAssignment( method, - "map key", - keySourceType, + getDescription(), keyTargetType, - null, // there is no targetPropertyName - keyDateFormat, - keyQualifiers, - keyQualifyingTargetType, - "entry.getKey()", - false + keyFormattingParameters, + keyCriteria, + keySourceRHS, + null, + () -> forge( keySourceRHS, keySourceType, keyTargetType, Message.MAPMAPPING_CREATE_KEY_NOTE ) ); if ( keyAssignment == null ) { @@ -136,26 +107,45 @@ public MapMappingMethod build() { return null; } else { - ctx.getMessager().printMessage( method.getExecutable(), - Message.MAPMAPPING_KEY_MAPPING_NOT_FOUND ); + reportCannotCreateMapping( + method, + String.format( + "%s \"%s\"", + keySourceRHS.getSourceErrorMessagePart(), + keySourceRHS.getSourceType().describe() + ), + keySourceRHS.getSourceType(), + keyTargetType, + "" + ); } } + else { + ctx.getMessager().note( 2, Message.MAPMAPPING_SELECT_KEY_NOTE, keyAssignment ); + } // find mapping method or conversion for value Type valueSourceType = sourceTypeParams.get( 1 ).getTypeBound(); Type valueTargetType = resultTypeParams.get( 1 ).getTypeBound(); + SourceRHS valueSourceRHS = new SourceRHS( "entry.getValue()", valueSourceType, new HashSet<>(), + "map value" ); + + SelectionCriteria valueCriteria = SelectionCriteria.forMappingMethods( + valueSelectionParameters, + method.getOptions().getMapMapping().getValueMappingControl( ctx.getElementUtils() ), + null, + false ); + Assignment valueAssignment = ctx.getMappingResolver().getTargetAssignment( method, - "map value", - valueSourceType, + getDescription(), valueTargetType, - null, // there is no targetPropertyName - valueDateFormat, - valueQualifiers, - valueQualifyingTargetType, - "entry.getValue()", - false + valueFormattingParameters, + valueCriteria, + valueSourceRHS, + null, + () -> forge( valueSourceRHS, valueSourceType, valueTargetType, Message.MAPMAPPING_CREATE_VALUE_NOTE ) ); if ( method instanceof ForgedMethod ) { @@ -174,63 +164,127 @@ public MapMappingMethod build() { return null; } else { - ctx.getMessager().printMessage( method.getExecutable(), - Message.MAPMAPPING_VALUE_MAPPING_NOT_FOUND ); + reportCannotCreateMapping( + method, + String.format( + "%s \"%s\"", + valueSourceRHS.getSourceErrorMessagePart(), + valueSourceRHS.getSourceType().describe() + ), + valueSourceRHS.getSourceType(), + valueTargetType, + "" + ); } } + else { + ctx.getMessager().note( 2, Message.MAPMAPPING_SELECT_VALUE_NOTE, valueAssignment ); + } - // mapNullToDefault - boolean mapNullToDefault = false; - if ( method.getMapperConfiguration() != null ) { - mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy ); + // mapNullToDefault — a JSpecify @NonNull return forces RETURN_DEFAULT to avoid generating `return null`. + // Forcing is unconditional here (unlike BeanMappingMethod): when the source is @NonNull the template skips + // the whole guard block via `sourceParameterPresenceCheck??`, so the forced value is simply unused there. + boolean mapNullToDefault = + method.getOptions().getMapMapping().getNullValueMappingStrategy().isReturnDefault(); + if ( !mapNullToDefault && ctx.isJSpecifyNonNullReturn( method ) ) { + ctx.getMessager().note( 2, + Message.MAPPING_METHOD_JSPECIFY_FORCE_RETURN_DEFAULT, + method.getName() ); + mapNullToDefault = true; } MethodReference factoryMethod = null; if ( !method.isUpdateMethod() ) { - factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null, null ); + factoryMethod = ObjectFactoryMethodResolver + .getFactoryMethod( method, null, ctx ); } - keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes() ); - valueAssignment = new LocalVarWrapper( valueAssignment, method.getThrownTypes() ); + keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes(), keyTargetType, false ); + valueAssignment = new LocalVarWrapper( valueAssignment, method.getThrownTypes(), valueTargetType, false ); + Set existingVariables = new HashSet<>( method.getParameterNames() ); List beforeMappingMethods = - LifecycleCallbackFactory.beforeMappingMethods( method, null, ctx ); + LifecycleMethodResolver.beforeMappingMethods( method, null, ctx, existingVariables ); List afterMappingMethods = - LifecycleCallbackFactory.afterMappingMethods( method, null, ctx ); + LifecycleMethodResolver.afterMappingMethods( method, null, ctx, existingVariables ); + + Parameter sourceParam = first( method.getSourceParameters() ); + PresenceCheck sourceParameterPresenceCheck = + PresenceCheckMethodResolver.getPresenceCheckForSourceParameter( method, null, sourceParam, ctx ); return new MapMappingMethod( method, + getMethodAnnotations(), + existingVariables, keyAssignment, valueAssignment, factoryMethod, mapNullToDefault, beforeMappingMethods, - afterMappingMethods + afterMappingMethods, + sourceParameterPresenceCheck ); } + + Assignment forge(SourceRHS sourceRHS, Type sourceType, Type targetType, Message message ) { + Assignment assignment = forgeMapping( sourceRHS, sourceType, targetType ); + if ( assignment != null ) { + ctx.getMessager().note( 2, message, assignment ); + } + return assignment; + } + + @Override + protected boolean shouldUsePropertyNamesInHistory() { + return true; + } + } - private MapMappingMethod(Method method, Assignment keyAssignment, Assignment valueAssignment, - MethodReference factoryMethod, boolean mapNullToDefault, + //CHECKSTYLE:OFF + private MapMappingMethod(Method method, List annotations, + Collection existingVariableNames, Assignment keyAssignment, + Assignment valueAssignment, MethodReference factoryMethod, boolean mapNullToDefault, List beforeMappingReferences, - List afterMappingReferences) { - super( method, beforeMappingReferences, afterMappingReferences ); + List afterMappingReferences, + PresenceCheck sourceParameterPresenceCheck) { + //CHECKSTYLE:ON + super( method, annotations, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences, + afterMappingReferences ); this.keyAssignment = keyAssignment; this.valueAssignment = valueAssignment; - this.factoryMethod = factoryMethod; - this.overridden = method.overridesMethod(); - this.mapNullToDefault = mapNullToDefault; - } - - public Parameter getSourceParameter() { + Parameter sourceParameter = null; for ( Parameter parameter : getParameters() ) { - if ( !parameter.isMappingTarget() ) { - return parameter; + if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { + sourceParameter = parameter; + break; } } - throw new IllegalStateException( "Method " + this + " has no source parameter." ); + if ( sourceParameter == null ) { + throw new IllegalStateException( "Method " + this + " has no source parameter." ); + } + + this.sourceParameter = sourceParameter; + this.sourceParameterPresenceCheck = sourceParameterPresenceCheck; + } + + public Parameter getSourceParameter() { + return sourceParameter; + } + + public PresenceCheck getSourceParameterPresenceCheck() { + return sourceParameterPresenceCheck; + } + + public List getSourceElementTypes() { + Type sourceParameterType = getSourceParameter().getType(); + return sourceParameterType.determineTypeArguments( Map.class ); + } + + public List getResultElementTypes() { + return getResultType().determineTypeArguments( Map.class ); } public Assignment getKeyAssignment() { @@ -251,86 +305,39 @@ public Set getImportTypes() { if ( valueAssignment != null ) { types.addAll( valueAssignment.getImportTypes() ); } - if ( ( factoryMethod == null ) && ( !isExistingInstanceMapping() ) ) { - types.addAll( getReturnType().getImportTypes() ); - if ( getReturnType().getImplementationType() != null ) { - types.addAll( getReturnType().getImplementationType().getImportTypes() ); - } + + if ( iterableCreation != null ) { + types.addAll( iterableCreation.getImportTypes() ); } return types; } public String getKeyVariableName() { - return Strings.getSaveVariableName( + return Strings.getSafeVariableName( "key", getParameterNames() ); } public String getValueVariableName() { - return Strings.getSaveVariableName( + return Strings.getSafeVariableName( "value", getParameterNames() ); } public String getEntryVariableName() { - return Strings.getSaveVariableName( + return Strings.getSafeVariableName( "entry", getParameterNames() ); } - public MethodReference getFactoryMethod() { - return this.factoryMethod; - } - - public boolean isMapNullToDefault() { - return mapNullToDefault; - } - - public boolean isOverridden() { - return overridden; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ( ( getResultType() == null ) ? 0 : getResultType().hashCode() ); - return result; - } - - @Override - public boolean equals(Object obj) { - if ( this == obj ) { - return true; - } - if ( obj == null ) { - return false; - } - if ( getClass() != obj.getClass() ) { - return false; + public IterableCreation getIterableCreation() { + if ( iterableCreation == null ) { + iterableCreation = IterableCreation.create( this, getSourceParameter() ); } - MapMappingMethod other = (MapMappingMethod) obj; - - if ( !getResultType().equals( other.getResultType() ) ) { - return false; - } - - if ( getSourceParameters().size() != other.getSourceParameters().size() ) { - return false; - } - - for ( int i = 0; i < getSourceParameters().size(); i++ ) { - if ( !getSourceParameters().get( i ).getType().getTypeParameters().get( 0 ) - .equals( other.getSourceParameters().get( i ).getType().getTypeParameters().get( 0 ) ) ) { - return false; - } - } - - return true; + return iterableCreation; } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java index ada616ee09..c261358455 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/Mapper.java @@ -1,29 +1,15 @@ -/** - * Copyright 2012-2015 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.model; import java.util.List; +import java.util.Set; import java.util.SortedSet; - -import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Type; @@ -31,6 +17,8 @@ import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.version.VersionInformation; +import static org.mapstruct.ap.internal.gem.ClassAccessibilityGem.DEFAULT; + /** * Represents a type implementing a mapper interface (annotated with {@code @Mapper}). This is the root object of the * mapper model. @@ -44,89 +32,42 @@ public class Mapper extends GeneratedType { static final String DEFAULT_IMPLEMENTATION_CLASS = CLASS_NAME_PLACEHOLDER + "Impl"; static final String DEFAULT_IMPLEMENTATION_PACKAGE = PACKAGE_NAME_PLACEHOLDER; - private final boolean customPackage; - private final boolean customImplName; - private final List referencedMappers; - private Decorator decorator; - - @SuppressWarnings( "checkstyle:parameternumber" ) - private Mapper(TypeFactory typeFactory, String packageName, String name, String superClassName, - String interfacePackage, String interfaceName, boolean customPackage, boolean customImplName, - List methods, Options options, VersionInformation versionInformation, - Accessibility accessibility, List referencedMappers, Decorator decorator, - SortedSet extraImportedTypes) { - - super( - typeFactory, - packageName, - name, - superClassName, - interfacePackage, - interfaceName, - methods, - referencedMappers, - options, - versionInformation, - accessibility, - extraImportedTypes, - null - ); - this.customPackage = customPackage; - this.customImplName = customImplName; - - this.referencedMappers = referencedMappers; - this.decorator = decorator; - } - - public static class Builder { + public static class Builder extends GeneratedTypeBuilder { - private TypeFactory typeFactory; private TypeElement element; - private List mappingMethods; - private List mapperReferences; - private SortedSet extraImportedTypes; + private List fields; + private Set fragments; - private Elements elementUtils; - private Options options; - private VersionInformation versionInformation; private Decorator decorator; private String implName; private boolean customName; private String implPackage; private boolean customPackage; + private boolean suppressGeneratorTimestamp; + private Set customAnnotations; + private Javadoc javadoc; - public Builder element(TypeElement element) { - this.element = element; - return this; - } - - public Builder mappingMethods(List mappingMethods) { - this.mappingMethods = mappingMethods; - return this; - } - - public Builder mapperReferences(List mapperReferences) { - this.mapperReferences = mapperReferences; - return this; + public Builder() { + super( Builder.class ); } - public Builder options(Options options) { - this.options = options; + public Builder element(TypeElement element) { + this.element = element; return this; } - public Builder versionInformation(VersionInformation versionInformation) { - this.versionInformation = versionInformation; + public Builder fields(List fields) { + this.fields = fields; return this; } - public Builder typeFactory(TypeFactory typeFactory) { - this.typeFactory = typeFactory; + public Builder constructorFragments(Set fragments) { + this.fragments = fragments; return this; } - public Builder elementUtils(Elements elementUtils) { - this.elementUtils = elementUtils; + public Builder additionalAnnotations(Set customAnnotations) { + this.customAnnotations = customAnnotations; return this; } @@ -135,11 +76,6 @@ public Builder decorator(Decorator decorator) { return this; } - public Builder extraImports(SortedSet extraImportedTypes) { - this.extraImportedTypes = extraImportedTypes; - return this; - } - public Builder implName(String implName) { this.implName = implName; this.customName = !DEFAULT_IMPLEMENTATION_CLASS.equals( this.implName ); @@ -152,35 +88,91 @@ public Builder implPackage(String implPackage) { return this; } + public Builder suppressGeneratorTimestamp(boolean suppressGeneratorTimestamp) { + this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; + return this; + } + + public Builder javadoc(Javadoc javadoc) { + this.javadoc = javadoc; + return this; + } + public Mapper build() { - String implementationName = implName.replace( CLASS_NAME_PLACEHOLDER, element.getSimpleName() ) + - ( decorator == null ? "" : "_" ); + String implementationName = implName.replace( CLASS_NAME_PLACEHOLDER, getFlatName( element ) ) + + ( decorator == null ? "" : "_" ); String elementPackage = elementUtils.getPackageOf( element ).getQualifiedName().toString(); String packageName = implPackage.replace( PACKAGE_NAME_PLACEHOLDER, elementPackage ); + Constructor constructor = null; + if ( !fragments.isEmpty() ) { + constructor = new NoArgumentConstructor( implementationName, fragments ); + } + + Type definitionType = typeFactory.getType( element ); + + Accessibility accessibility = + classAccessibility == DEFAULT ? Accessibility.fromModifiers( element.getModifiers() ) : + Accessibility.valueOf( classAccessibility.name() ); return new Mapper( typeFactory, packageName, implementationName, - element.getKind() != ElementKind.INTERFACE ? element.getSimpleName().toString() : null, - elementPackage, - element.getKind() == ElementKind.INTERFACE ? element.getSimpleName().toString() : null, + definitionType, customPackage, customName, - mappingMethods, + customAnnotations, + methods, options, versionInformation, - Accessibility.fromModifiers( element.getModifiers() ), - mapperReferences, + suppressGeneratorTimestamp, + accessibility, + fields, + constructor, decorator, - extraImportedTypes + extraImportedTypes, + javadoc ); } + } - public List getReferencedMappers() { - return referencedMappers; + private final boolean customPackage; + private final boolean customImplName; + private Decorator decorator; + private final Javadoc javadoc; + + @SuppressWarnings( "checkstyle:parameternumber" ) + private Mapper(TypeFactory typeFactory, String packageName, String name, + Type mapperDefinitionType, + boolean customPackage, boolean customImplName, Set customAnnotations, + List methods, Options options, VersionInformation versionInformation, + boolean suppressGeneratorTimestamp, + Accessibility accessibility, List fields, Constructor constructor, + Decorator decorator, SortedSet extraImportedTypes, Javadoc javadoc ) { + + super( + typeFactory, + packageName, + name, + mapperDefinitionType, + methods, + fields, + options, + versionInformation, + suppressGeneratorTimestamp, + accessibility, + extraImportedTypes, + constructor + ); + this.customPackage = customPackage; + this.customImplName = customImplName; + customAnnotations.forEach( this::addAnnotation ); + + this.decorator = decorator; + + this.javadoc = javadoc; } public Decorator getDecorator() { @@ -191,17 +183,37 @@ public void removeDecorator() { this.decorator = null; } - /** - * Checks if the mapper has a custom implementation that is a custom suffix of an explicit destination package. - * - * @return - */ public boolean hasCustomImplementation() { return customImplName || customPackage; } + @Override + public Javadoc getJavadoc() { + return javadoc; + } + @Override protected String getTemplateName() { return getTemplateNameForClass( GeneratedType.class ); } + + /** + * Returns the same as {@link Class#getName()} but without the package declaration. + * + * @param element the element that should be flattened + * + * @return the flat name for the type element + */ + public static String getFlatName(TypeElement element) { + if ( !(element.getEnclosingElement() instanceof TypeElement) ) { + return element.getSimpleName().toString(); + } + StringBuilder nameBuilder = new StringBuilder( element.getSimpleName().toString() ); + for ( Element enclosing = element.getEnclosingElement(); enclosing instanceof TypeElement; enclosing = + enclosing.getEnclosingElement() ) { + nameBuilder.insert( 0, '$' ); + nameBuilder.insert( 0, enclosing.getSimpleName().toString() ); + } + return nameBuilder.toString(); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MapperReference.java index 3ef1fc415e..0179bd6324 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MapperReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MapperReference.java @@ -1,24 +1,14 @@ -/** - * Copyright 2012-2015 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.model; +import java.util.List; + import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.SourceMethod; /** * A reference to another mapper class, which itself may be generated or hand-written. @@ -34,4 +24,15 @@ public MapperReference(Type type, String variableName) { public MapperReference(Type type, String variableName, boolean isUsed) { super( type, variableName, isUsed ); } + + public static MapperReference findMapperReference(List mapperReferences, SourceMethod method) { + for ( MapperReference ref : mapperReferences ) { + if ( ref.getType().equals( method.getDeclaringMapper() ) ) { + ref.setUsed( ref.isUsed() || !method.isStatic() ); + ref.setTypeRequiresImport( true ); + return ref; + } + } + return null; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java index a63a067675..39e8558e5e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java @@ -1,39 +1,40 @@ -/** - * Copyright 2012-2015 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.model; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; - +import java.util.function.Supplier; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import org.mapstruct.ap.internal.model.assignment.Assignment; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.util.AccessorNamingUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.NullabilityResolver; +import org.mapstruct.ap.internal.util.Services; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.internal.version.VersionInformation; +import org.mapstruct.ap.spi.EnumMappingStrategy; +import org.mapstruct.ap.spi.EnumTransformationStrategy; +import org.mapstruct.ap.spi.MappingExclusionProvider; /** * This class provides the context for the builders. @@ -42,7 +43,7 @@ *

      *
    • Input for the building process, such as the source model (mapping methods found) and mapper references.
    • *
    • Required factory, utility, reporting methods for building the mappings.
    • - *
    • Means to harbor results produced by the builders, such as forged- and virtual mapping methods that should be + *
    • Means to harbor results produced by the builders, such as forged- and supported mapping methods that should be * generated in a later stage.
    • *
    * @@ -50,6 +51,11 @@ */ public class MappingBuilderContext { + private static final MappingExclusionProvider SUB_MAPPING_EXCLUSION_PROVIDER = Services.get( + MappingExclusionProvider.class, + new DefaultMappingExclusionProvider() + ); + /** * Resolves the most suitable way for mapping an element (property, iterable element etc.) from source to target. * There are 2 basic types of mappings: @@ -74,62 +80,61 @@ public interface MappingResolver { * returns a parameter assignment * * @param mappingMethod target mapping method - * @param mappedElement used for error messages - * @param sourceType parameter to match + * @param description the description source * @param targetType return type to match - * @param targetPropertyName name of the target property - * @param dateFormat used for formatting dates in build in methods that need context information - * @param qualifiers used for further select the appropriate mapping method based on class and name - * @param resultType used for further select the appropriate mapping method based on resultType (bean mapping) - * targetType (Iterable- and MapMapping) - * @param sourceReference call to source type as string - * @param preferUpdateMethods selection should prefer update methods when present. + * @param formattingParameters used for formatting dates and numbers + * @param criteria parameters criteria in the selection process + * @param sourceRHS source information + * @param positionHint the mirror for reporting problems + * @param forger the supplier of the callback method to forge a method * * @return an assignment to a method parameter, which can either be: *
      *
    1. MethodReference
    2. *
    3. TypeConversion
    4. - *
    5. Direct Assignment (empty TargetAssignment)
    6. + *
    7. SourceRHS Assignment (empty TargetAssignment)
    8. *
    9. null, no assignment found
    10. *
    */ - Assignment getTargetAssignment(Method mappingMethod, String mappedElement, Type sourceType, Type targetType, - String targetPropertyName, String dateFormat, List qualifiers, - TypeMirror resultType, String sourceReference, boolean preferUpdateMethods); + Assignment getTargetAssignment(Method mappingMethod, ForgedMethodHistory description, Type targetType, + FormattingParameters formattingParameters, + SelectionCriteria criteria, SourceRHS sourceRHS, + AnnotationMirror positionHint, + Supplier forger); - /** - * returns a no arg factory method - * - * @param mappingMethod target mapping method - * @param target return type to match - * @param qualifiers used for further select the appropriate mapping method based on class and name - * @param resultType used for further select the appropriate mapping method based on resultType (bean mapping) - * targetType (Iterable- and MapMapping) * - * - * @return a method reference to the factory method, or null if no suitable, or ambiguous method found - * - */ - MethodReference getFactoryMethod(Method mappingMethod, Type target, List qualifiers, - TypeMirror resultType); + Set getUsedSupportedMappings(); - Set getUsedVirtualMappings(); + Set getUsedSupportedFields(); } private final TypeFactory typeFactory; - private final Elements elementUtils; - private final Types typeUtils; + private final ElementUtils elementUtils; + private final TypeUtils typeUtils; private final FormattingMessager messager; + private final VersionInformation versionInformation; + private final AccessorNamingUtils accessorNaming; + private final NullabilityResolver nullabilityResolver; + private final EnumMappingStrategy enumMappingStrategy; + private final Map enumTransformationStrategies; private final Options options; private final TypeElement mapperTypeElement; private final List sourceModel; private final List mapperReferences; private final MappingResolver mappingResolver; - private final List mappingsToGenerate = new ArrayList(); + private final List mappingsToGenerate = new ArrayList<>(); + private final Map forgedMethodsUnderCreation = + new HashMap<>(); + //CHECKSTYLE:OFF public MappingBuilderContext(TypeFactory typeFactory, - Elements elementUtils, - Types typeUtils, + ElementUtils elementUtils, + TypeUtils typeUtils, FormattingMessager messager, + VersionInformation versionInformation, + AccessorNamingUtils accessorNaming, + NullabilityResolver nullabilityResolver, + EnumMappingStrategy enumMappingStrategy, + Map enumTransformationStrategies, Options options, MappingResolver mappingResolver, TypeElement mapper, @@ -139,12 +144,31 @@ public MappingBuilderContext(TypeFactory typeFactory, this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; + this.versionInformation = versionInformation; + this.accessorNaming = accessorNaming; + this.nullabilityResolver = nullabilityResolver; + this.enumMappingStrategy = enumMappingStrategy; + this.enumTransformationStrategies = enumTransformationStrategies; this.options = options; this.mappingResolver = mappingResolver; this.mapperTypeElement = mapper; this.sourceModel = sourceModel; this.mapperReferences = mapperReferences; } + //CHECKSTYLE:ON + + /** + * Returns a map which is used to track which forged methods are under creation. + * Used for cutting the possible infinite recursion of forged method creation. + * + * Map is used instead of set because not all fields of ForgedMethods are used in equals/hashCode and we are + * interested only in the first created ForgedMethod + * + * @return map of forged methods + */ + public Map getForgedMethodsUnderCreation() { + return forgedMethodsUnderCreation; + } public TypeElement getMapperTypeElement() { return mapperTypeElement; @@ -162,11 +186,11 @@ public TypeFactory getTypeFactory() { return typeFactory; } - public Elements getElementUtils() { + public ElementUtils getElementUtils() { return elementUtils; } - public Types getTypeUtils() { + public TypeUtils getTypeUtils() { return typeUtils; } @@ -174,6 +198,60 @@ public FormattingMessager getMessager() { return messager; } + public VersionInformation getVersionInformation() { + return versionInformation; + } + + public AccessorNamingUtils getAccessorNaming() { + return accessorNaming; + } + + public NullabilityResolver getNullabilityResolver() { + return nullabilityResolver; + } + + /** + * Resolves the JSpecify nullability of an element declared directly on the mapper (e.g. a mapping method's + * return type or one of its source parameters), using the mapper type's {@code @NullMarked} scope as the + * enclosing scope for unannotated elements. + * + * @param element the element declared on the mapper to inspect + * + * @return the resolved nullability ({@link NullabilityResolver.Nullability#UNKNOWN} when JSpecify is disabled) + */ + public NullabilityResolver.Nullability getNullabilityInMapperScope(Element element) { + return nullabilityResolver.getNullability( + element, + () -> typeFactory.getType( mapperTypeElement.asType() ).isNullMarked() ); + } + + /** + * Whether the return type of the given mapping method is JSpecify {@code @NonNull} (directly or via a + * {@code @NullMarked} scope). When it is, a mapping method must not generate {@code return null}, so callers + * force {@link org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem#RETURN_DEFAULT} semantics. + *

    + * Update methods and {@code void}-returning methods never generate a {@code return null} and are excluded. + * + * @param method the mapping method to inspect + * + * @return {@code true} if the return type is {@code @NonNull}, {@code false} otherwise + */ + public boolean isJSpecifyNonNullReturn(Method method) { + if ( method.isUpdateMethod() || method.getReturnType().isVoid() ) { + return false; + } + + return getNullabilityInMapperScope( method.getExecutable() ) == NullabilityResolver.Nullability.NON_NULL; + } + + public EnumMappingStrategy getEnumMappingStrategy() { + return enumMappingStrategy; + } + + public Map getEnumTransformationStrategies() { + return enumTransformationStrategies; + } + public Options getOptions() { return options; } @@ -186,12 +264,18 @@ public List getMappingsToGenerate() { return mappingsToGenerate; } - public List getNamesOfMappingsToGenerate() { - List nameList = new ArrayList(); + public List getReservedNames() { + Set nameSet = new HashSet<>(); for ( MappingMethod method : mappingsToGenerate ) { - nameList.add( method.getName() ); + nameSet.add( method.getName() ); + } + // add existing names + for ( SourceMethod method : sourceModel ) { + if ( method.isAbstract() ) { + nameSet.add( method.getName() ); + } } - return nameList; + return new ArrayList<>( nameSet ); } public MappingMethod getExistingMappingMethod(MappingMethod newMappingMethod) { @@ -205,8 +289,38 @@ public MappingMethod getExistingMappingMethod(MappingMethod newMappingMethod) { return existingMappingMethod; } - public Set getUsedVirtualMappings() { - return mappingResolver.getUsedVirtualMappings(); + public Set getUsedSupportedMappings() { + return mappingResolver.getUsedSupportedMappings(); } + public Set getUsedSupportedFields() { + return mappingResolver.getUsedSupportedFields(); + } + + /** + * @param sourceType from which an automatic sub-mapping needs to be generated + * @param targetType to which an automatic sub-mapping needs to be generated + * + * @return {@code true} if MapStruct is allowed to try and generate an automatic sub-mapping between the + * source and target {@link Type} + */ + public boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType) { + return canGenerateAutoSubMappingFor( sourceType ) && canGenerateAutoSubMappingFor( targetType ); + } + + /** + * @param type that MapStruct wants to use to generate an automatic sub-mapping for/from + * + * @return {@code true} if the type is not excluded from the {@link MappingExclusionProvider} + */ + private boolean canGenerateAutoSubMappingFor(Type type) { + if ( "java.util.Optional".equals( type.getFullyQualifiedName() ) ) { + return !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getOptionalBaseType().getTypeElement() ); + } + return type.getTypeElement() != null && !SUB_MAPPING_EXCLUSION_PROVIDER.isExcluded( type.getTypeElement() ); + } + + public boolean isErroneous() { + return messager.isErroneous(); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java index 4f9705b464..0d04f5b6d5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingMethod.java @@ -1,36 +1,24 @@ -/** - * Copyright 2012-2015 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.model; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import org.mapstruct.ap.internal.model.common.Accessibility; -import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; -import static org.mapstruct.ap.internal.util.Strings.getSaveVariableName; +import static org.mapstruct.ap.internal.util.Strings.getSafeVariableName; import static org.mapstruct.ap.internal.util.Strings.join; /** @@ -38,10 +26,11 @@ * * @author Gunnar Morling */ -public abstract class MappingMethod extends ModelElement { +public abstract class MappingMethod extends GeneratedTypeMethod { private final String name; private final List parameters; + private final List sourceParameters; private final Type returnType; private final Parameter targetParameter; private final Accessibility accessibility; @@ -56,14 +45,23 @@ public abstract class MappingMethod extends ModelElement { * constructor to be overloaded when local variable names are required prior to calling this constructor. (e.g. for * property mappings). It is supposed to be initialized with at least the parameter names. * - * @param method method - * @param existingVariableNames existingVariableNames + * @param method the method for which this mapping is applicable + * @param existingVariableNames set of already assigned variable names + * @param beforeMappingReferences all life cycle methods to be called prior to carrying out mapping + * @param afterMappingReferences all life cycle methods to be called after carrying out mapping */ protected MappingMethod(Method method, Collection existingVariableNames, List beforeMappingReferences, List afterMappingReferences) { + this( method, method.getParameters(), existingVariableNames, beforeMappingReferences, afterMappingReferences ); + } + + protected MappingMethod(Method method, List parameters, Collection existingVariableNames, + List beforeMappingReferences, + List afterMappingReferences) { this.name = method.getName(); - this.parameters = method.getParameters(); + this.parameters = parameters; + this.sourceParameters = Parameter.getSourceParameters( parameters ); this.returnType = method.getReturnType(); this.targetParameter = method.getMappingTargetParameter(); this.accessibility = method.getAccessibility(); @@ -72,16 +70,21 @@ protected MappingMethod(Method method, Collection existingVariableNames, this.resultName = initResultName( existingVariableNames ); this.beforeMappingReferencesWithMappingTarget = filterMappingTarget( beforeMappingReferences, true ); this.beforeMappingReferencesWithoutMappingTarget = filterMappingTarget( beforeMappingReferences, false ); - this.afterMappingReferences = afterMappingReferences; + this.afterMappingReferences = afterMappingReferences == null ? Collections.emptyList() : afterMappingReferences; + } + + protected MappingMethod(Method method, List parameters) { + this( method, parameters, new ArrayList<>( method.getParameterNames() ), null, null ); } protected MappingMethod(Method method) { - this( method, method.getParameterNames(), null, null ); + this( method, new ArrayList<>( method.getParameterNames() ), null, null ); } protected MappingMethod(Method method, List beforeMappingReferences, List afterMappingReferences) { - this( method, method.getParameterNames(), beforeMappingReferences, afterMappingReferences ); + this( method, new ArrayList<>( method.getParameterNames() ), beforeMappingReferences, + afterMappingReferences ); } private String initResultName(Collection existingVarNames) { @@ -89,12 +92,16 @@ private String initResultName(Collection existingVarNames) { return targetParameter.getName(); } else if ( getResultType().isArrayType() ) { - String name = getSaveVariableName( getResultType().getComponentType().getName() + "Tmp", existingVarNames ); + String name = getSafeVariableName( getResultType().getComponentType().getName() + "Tmp", existingVarNames ); existingVarNames.add( name ); return name; } else { - String name = getSaveVariableName( getResultType().getName(), existingVarNames ); + Type resultType = getResultType(); + if ( resultType.isOptionalType() ) { + resultType = resultType.getOptionalBaseType(); + } + String name = getSafeVariableName( resultType.getName(), existingVarNames ); existingVarNames.add( name ); return name; } @@ -109,14 +116,6 @@ public List getParameters() { } public List getSourceParameters() { - List sourceParameters = new ArrayList(); - - for ( Parameter parameter : parameters ) { - if ( !parameter.isMappingTarget() ) { - sourceParameters.add( parameter ); - } - } - return sourceParameters; } @@ -146,19 +145,33 @@ public boolean isStatic() { @Override public Set getImportTypes() { - Set types = new HashSet(); + Set types = new HashSet<>(); for ( Parameter param : parameters ) { - types.add( param.getType() ); + types.addAll( param.getType().getImportTypes() ); + } + + types.addAll( getReturnType().getImportTypes() ); + + for ( Type type : thrownTypes ) { + types.addAll( type.getImportTypes() ); + } + + for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithMappingTarget ) { + types.addAll( reference.getImportTypes() ); + } + for ( LifecycleCallbackMethodReference reference : beforeMappingReferencesWithoutMappingTarget ) { + types.addAll( reference.getImportTypes() ); + } + for ( LifecycleCallbackMethodReference reference : afterMappingReferences ) { + types.addAll( reference.getImportTypes() ); } - types.add( getReturnType() ); - types.addAll( thrownTypes ); return types; } protected List getParameterNames() { - List parameterNames = new ArrayList( parameters.size() ); + List parameterNames = new ArrayList<>( parameters.size() ); for ( Parameter parameter : parameters ) { parameterNames.add( parameter.getName() ); @@ -176,14 +189,14 @@ public String toString() { return returnType + " " + getName() + "(" + join( parameters, ", " ) + ")"; } - private List filterMappingTarget(List methods, - boolean mustHaveMappingTargetParameter) { + protected static List filterMappingTarget( + List methods, boolean mustHaveMappingTargetParameter) { if ( methods == null ) { - return null; + return Collections.emptyList(); } List result = - new ArrayList( methods.size() ); + new ArrayList<>( methods.size() ); for ( LifecycleCallbackMethodReference method : methods ) { if ( mustHaveMappingTargetParameter == method.hasMappingTargetParameter() ) { @@ -205,4 +218,39 @@ public List getBeforeMappingReferencesWithMapp public List getBeforeMappingReferencesWithoutMappingTarget() { return beforeMappingReferencesWithoutMappingTarget; } + + @Override + public int hashCode() { + int hash = 7; + hash = 83 * hash + (this.parameters != null ? this.parameters.hashCode() : 0); + hash = 83 * hash + (this.returnType != null ? this.returnType.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + //Do not add name to the equals check. + //Reason: Whenever we forge methods we can reuse mappings if they are the same. However, if we take the name + // into consideration, they'll never be the same, because we create safe methods names. + final MappingMethod other = (MappingMethod) obj; + + if ( !Objects.equals( parameters, other.parameters ) ) { + return false; + } + + if ( !Objects.equals( returnType, other.returnType ) ) { + return false; + } + + return true; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java index f2448e7af4..e2e518c55b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java @@ -1,35 +1,30 @@ -/** - * Copyright 2012-2015 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.model; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; -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.common.ModelElement; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; +import org.mapstruct.ap.internal.util.Strings; /** * Represents a reference to another method, e.g. used to map a bean property from source to target type or to @@ -37,8 +32,10 @@ * * @author Gunnar Morling */ -public class MethodReference extends MappingMethod implements Assignment { - +public class MethodReference extends ModelElement implements Assignment { + private final String name; + private final List sourceParameters; + private final Type returnType; private final MapperReference declaringMapper; private final Set importTypes; private final List thrownTypes; @@ -51,55 +48,154 @@ public class MethodReference extends MappingMethod implements Assignment { */ private final String contextParam; - /** * A reference to another mapping method or typeConversion in case this is a two-step mapping, e.g. from * {@code JAXBElement} to {@code Foo} to for which a nested method call will be generated: - * {@code setFoo(barToFoo( jaxbElemToValue( bar) ) )} - * - * If there's no nested typeConversion or other mapping method, this will be a {@link Direct} assignment. + * {@code setFoo(barToFoo( jaxbElemToValue( bar) ) )}. If there's no nested typeConversion or other mapping method, + * this will be a direct assignment. */ private Assignment assignment; - private final Type definingType; + private final List parameterBindings; + private final Parameter providingParameter; + private final List methodsToChain; + private final boolean isStatic; + private final boolean isConstructor; + private final boolean isMethodChaining; /** * Creates a new reference to the given method. * * @param method the target method of the reference * @param declaringMapper the method declaring the mapper; {@code null} if the current mapper itself - * @param targetType in case the referenced method has a parameter for passing the target type, the given - * target type, otherwise {@code null} + * @param providingParameter The parameter providing the mapper, or {@code null} if the method is defined by the + * mapper itself or by {@code declaringMapper}. + * @param parameterBindings the parameter bindings of this method reference */ - public MethodReference(Method method, MapperReference declaringMapper, Type targetType) { - super( method ); + protected MethodReference(Method method, MapperReference declaringMapper, Parameter providingParameter, + List parameterBindings) { this.declaringMapper = declaringMapper; + this.sourceParameters = Parameter.getSourceParameters( method.getParameters() ); + this.returnType = method.getReturnType(); + this.providingParameter = providingParameter; + this.parameterBindings = parameterBindings; this.contextParam = null; - Set imported = new HashSet( method.getThrownTypes() ); - if ( targetType != null ) { - imported.add( targetType ); + Set imported = new HashSet<>(); + + for ( Type type : method.getThrownTypes() ) { + imported.addAll( type.getImportTypes() ); } - this.importTypes = Collections.unmodifiableSet( imported ); + + for ( ParameterBinding binding : parameterBindings ) { + imported.addAll( binding.getImportTypes() ); + } + + this.importTypes = Collections.unmodifiableSet( imported ); this.thrownTypes = method.getThrownTypes(); this.isUpdateMethod = method.getMappingTargetParameter() != null; this.definingType = method.getDefiningType(); + this.isStatic = method.isStatic(); + this.name = method.getName(); + this.isConstructor = false; + this.methodsToChain = Collections.emptyList(); + this.isMethodChaining = false; } - public MethodReference(BuiltInMethod method, ConversionContext contextParam) { - super( method ); + private MethodReference(BuiltInMethod method, ConversionContext contextParam) { + this.sourceParameters = Parameter.getSourceParameters( method.getParameters() ); + this.returnType = method.getReturnType(); this.declaringMapper = null; + this.providingParameter = null; this.contextParam = method.getContextParameter( contextParam ); this.importTypes = Collections.emptySet(); this.thrownTypes = Collections.emptyList(); this.definingType = null; this.isUpdateMethod = method.getMappingTargetParameter() != null; + this.parameterBindings = ParameterBinding.fromParameters( method.getParameters() ); + this.isStatic = method.isStatic(); + this.name = method.getName(); + this.isConstructor = false; + this.methodsToChain = Collections.emptyList(); + this.isMethodChaining = false; + } + + private MethodReference(String name, Type definingType, boolean isStatic) { + this.name = name; + this.definingType = definingType; + this.sourceParameters = Collections.emptyList(); + this.returnType = null; + this.declaringMapper = null; + this.importTypes = Collections.emptySet(); + this.thrownTypes = Collections.emptyList(); + this.isUpdateMethod = false; + this.contextParam = null; + this.parameterBindings = Collections.emptyList(); + this.providingParameter = null; + this.isStatic = isStatic; + this.isConstructor = false; + this.methodsToChain = Collections.emptyList(); + this.isMethodChaining = false; + } + + private MethodReference(Type definingType, List parameterBindings) { + this.name = null; + this.definingType = definingType; + this.sourceParameters = Collections.emptyList(); + this.returnType = null; + this.declaringMapper = null; + this.thrownTypes = Collections.emptyList(); + this.isUpdateMethod = false; + this.contextParam = null; + this.parameterBindings = parameterBindings; + this.providingParameter = null; + this.isStatic = false; + this.isConstructor = true; + this.methodsToChain = Collections.emptyList(); + this.isMethodChaining = false; + + if ( parameterBindings.isEmpty() ) { + this.importTypes = Collections.emptySet(); + } + else { + Set imported = new LinkedHashSet<>(); + + for ( ParameterBinding binding : parameterBindings ) { + imported.add( binding.getType() ); + } + + imported.add( definingType ); + + this.importTypes = Collections.unmodifiableSet( imported ); + } + } + + private MethodReference(MethodReference... references) { + this.name = null; + this.definingType = null; + this.sourceParameters = Collections.emptyList(); + this.returnType = null; + this.declaringMapper = null; + this.importTypes = Collections.emptySet(); + this.thrownTypes = Collections.emptyList(); + this.isUpdateMethod = false; + this.contextParam = null; + this.parameterBindings = null; + this.providingParameter = null; + this.isStatic = false; + this.isConstructor = false; + this.methodsToChain = Arrays.asList( references ); + this.isMethodChaining = true; } public MapperReference getDeclaringMapper() { return declaringMapper; } + public Parameter getProvidingParameter() { + return providingParameter; + } + public String getMapperVariableName() { return declaringMapper.getVariableName(); } @@ -112,6 +208,14 @@ public Assignment getAssignment() { return assignment; } + public String getName() { + return name; + } + + public List getSourceParameters() { + return sourceParameters; + } + @Override public void setAssignment( Assignment assignment ) { this.assignment = assignment; @@ -119,7 +223,52 @@ public void setAssignment( Assignment assignment ) { @Override public String getSourceReference() { - return assignment.getSourceReference(); + return assignment != null ? assignment.getSourceReference() : null; + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return assignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return assignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName ) { + return assignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return assignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + assignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return assignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + assignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return assignment.getSourceParameterName(); + } + + @Override + public boolean isSourceReferenceParameter() { + return assignment.isSourceReferenceParameter(); } /** @@ -140,23 +289,34 @@ public Type getDefiningType() { @Override public Set getImportTypes() { - Set imported = new HashSet( importTypes ); + Set imported = new HashSet<>( importTypes ); if ( assignment != null ) { imported.addAll( assignment.getImportTypes() ); } if ( isStatic() ) { imported.add( definingType ); } + if ( isMethodChaining() ) { + for ( MethodReference methodToChain : methodsToChain ) { + imported.addAll( methodToChain.getImportTypes() ); + } + } + return imported; } @Override public List getThrownTypes() { - List exceptions = new ArrayList(); - exceptions.addAll( thrownTypes ); + List exceptions = new ArrayList<>( thrownTypes ); if ( assignment != null ) { exceptions.addAll( assignment.getThrownTypes() ); } + if ( isMethodChaining() ) { + for ( MethodReference methodToChain : methodsToChain ) { + exceptions.addAll( methodToChain.getThrownTypes() ); + } + + } return exceptions; } @@ -175,8 +335,119 @@ public AssignmentType getType() { } } + public Type getReturnType() { + return returnType; + } + @Override - public boolean isUpdateMethod() { + public boolean isCallingUpdateMethod() { return isUpdateMethod; } + + public boolean isStatic() { + return isStatic; + } + + public boolean isConstructor() { + return isConstructor; + } + + public boolean isMethodChaining() { + return isMethodChaining; + } + + public List getMethodsToChain() { + return methodsToChain; + } + + public List getParameterBindings() { + return parameterBindings; + } + + public Type inferTypeWhenEnum( Type type ) { + if ( "java.lang.Enum".equals( type.getFullyQualifiedName() ) ) { + return type.getTypeParameters().get( 0 ); + } + else { + return type; + } + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ( ( declaringMapper == null ) ? 0 : declaringMapper.hashCode() ); + result = prime * result + ( ( providingParameter == null ) ? 0 : providingParameter.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( !super.equals( obj ) ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + MethodReference other = (MethodReference) obj; + if ( !Objects.equals( declaringMapper, other.declaringMapper ) ) { + return false; + } + if ( !Objects.equals( providingParameter, other.providingParameter ) ) { + return false; + } + + return true; + } + + public static MethodReference forBuiltInMethod(BuiltInMethod method, ConversionContext contextParam) { + return new MethodReference( method, contextParam ); + } + + public static MethodReference forForgedMethod(Method method, List parameterBindings) { + return new MethodReference( method, null, null, parameterBindings ); + } + + public static MethodReference forParameterProvidedMethod(Method method, Parameter providingParameter, + List parameterBindings) { + return new MethodReference( method, null, providingParameter, parameterBindings ); + } + + public static MethodReference forMapperReference(Method method, MapperReference declaringMapper, + List parameterBindings) { + return new MethodReference( method, declaringMapper, null, parameterBindings ); + } + + public static MethodReference forStaticBuilder(String builderCreationMethod, Type definingType) { + return new MethodReference( builderCreationMethod, definingType, true ); + } + + public static MethodReference forMethodCall(String methodName) { + return new MethodReference( methodName, null, false ); + } + + public static MethodReference forConstructorInvocation(Type type, List parameterBindings) { + return new MethodReference( type, parameterBindings ); + } + + public static MethodReference forMethodChaining(MethodReference... references) { + return new MethodReference( references ); + } + + @Override + public String toString() { + String mapper = declaringMapper != null ? declaringMapper.getType().getName() : ""; + String argument = getAssignment() != null ? getAssignment().toString() : + ( getSourceReference() != null ? getSourceReference() : "" ); + String returnTypeAsString = returnType != null ? returnType.toString() : ""; + List arguments = sourceParameters.stream() + .map( p -> p.isMappingContext() || p.isMappingTarget() || p.isTargetType() ? p.getName() : argument ) + .collect( Collectors.toList() ); + + return returnTypeAsString + " " + mapper + "#" + name + "(" + Strings.join( arguments, "," ) + ")"; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java new file mode 100644 index 0000000000..29904ff416 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.java @@ -0,0 +1,68 @@ +/* + * 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.model; + +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * A {@link PresenceCheck} that is based on a {@link MethodReference}. + * + * @author Filip Hrisafov + */ +public class MethodReferencePresenceCheck extends ModelElement implements PresenceCheck { + + protected final MethodReference methodReference; + protected final boolean negate; + + public MethodReferencePresenceCheck(MethodReference methodReference) { + this( methodReference, false ); + } + + public MethodReferencePresenceCheck(MethodReference methodReference, boolean negate) { + this.methodReference = methodReference; + this.negate = negate; + } + + @Override + public Set getImportTypes() { + return methodReference.getImportTypes(); + } + + public MethodReference getMethodReference() { + return methodReference; + } + + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new MethodReferencePresenceCheck( methodReference, true ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + MethodReferencePresenceCheck that = (MethodReferencePresenceCheck) o; + return Objects.equals( methodReference, that.methodReference ); + } + + @Override + public int hashCode() { + return Objects.hash( methodReference ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index 16cbcee183..79341b8921 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -1,32 +1,27 @@ -/** - * Copyright 2012-2015 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.model; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; +import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.SourceReference.PropertyEntry; +import org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; +import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; +import org.mapstruct.ap.internal.util.NullabilityResolver; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; /** * This method is used to convert the nested properties as listed in propertyEntries into a method @@ -43,10 +38,11 @@ public class NestedPropertyMappingMethod extends MappingMethod { public static class Builder { - private Method method; + private MappingBuilderContext ctx; + private ForgedMethod method; private List propertyEntries; - public Builder method( Method sourceMethod ) { + public Builder method( ForgedMethod sourceMethod ) { this.method = sourceMethod; return this; } @@ -56,29 +52,128 @@ public Builder propertyEntries( List propertyEntries ) { return this; } + public Builder mappingContext(MappingBuilderContext mappingContext) { + this.ctx = mappingContext; + return this; + } + public NestedPropertyMappingMethod build() { - List existingVariableNames = new ArrayList(); + List existingVariableNames = new ArrayList<>(); + Parameter sourceParameter = null; for ( Parameter parameter : method.getSourceParameters() ) { existingVariableNames.add( parameter.getName() ); + if ( sourceParameter == null && !parameter.isMappingTarget() && !parameter.isMappingContext() ) { + sourceParameter = parameter; + } } - List safePropertyEntries = new ArrayList(); - for ( PropertyEntry propertyEntry : propertyEntries ) { - String safeName = Strings.getSaveVariableName( propertyEntry.getName(), existingVariableNames); - safePropertyEntries.add( new SafePropertyEntry( propertyEntry, safeName ) ); + final List thrownTypes = new ArrayList<>(); + List safePropertyEntries = new ArrayList<>(); + if ( sourceParameter == null ) { + throw new IllegalStateException( "Method " + method + " has no source parameter." ); + } + + String previousPropertyName = sourceParameter.getName(); + Type previousPropertyType = sourceParameter.getType(); + boolean previousEntryIsNonNull = false; + for ( int i = 0; i < propertyEntries.size(); i++ ) { + PropertyEntry propertyEntry = propertyEntries.get( i ); + PresenceCheck presenceCheck; + boolean currentEntryIsNonNull = ctx.getNullabilityResolver().getNullability( + propertyEntry.getReadAccessor().getElement(), + previousPropertyType::isNullMarked + ) == NullabilityResolver.Nullability.NON_NULL; + + if ( previousPropertyType.isOptionalType() ) { + String optionalValueSafeName = Strings.getSafeVariableName( + previousPropertyName + "Value", + existingVariableNames + ); + existingVariableNames.add( optionalValueSafeName ); + + presenceCheck = getPresenceCheck( propertyEntry, optionalValueSafeName ); + + String optionalValueSource = previousPropertyName + ".get()"; + boolean doesNotNeedFollowUpProperty = false; + if ( i == propertyEntries.size() - 1 ) { + // If this is the last property, and we do not have a presence check, + // then we do not need to assign the optional value + // e.g., we need to generate .get().getXxx(); + doesNotNeedFollowUpProperty = presenceCheck == null; + if ( doesNotNeedFollowUpProperty ) { + optionalValueSource += "." + propertyEntry.getReadAccessor().getReadValueSource(); + } + } + Type optionalBaseType = previousPropertyType.getOptionalBaseType(); + safePropertyEntries.add( new SafePropertyEntry( + optionalBaseType, + optionalValueSafeName, + optionalValueSource, + new OptionalPresenceCheck( previousPropertyName, ctx.getVersionInformation(), true ) + ) ); + if ( doesNotNeedFollowUpProperty ) { + break; + } + previousPropertyName = optionalValueSafeName; + + } + else { + presenceCheck = getPresenceCheck( propertyEntry, previousPropertyName ); + if ( i > 0 && !previousEntryIsNonNull ) { + // If this is not the first property entry, + // then we need to combine the presence check with a null check of the previous property. + // JSpecify: the null check is skipped when the previous accessor returns @NonNull. + if ( presenceCheck != null ) { + presenceCheck = new AnyPresenceChecksPresenceCheck( Arrays.asList( + new NullPresenceCheck( previousPropertyName, true ), + presenceCheck + ) ); + } + else { + presenceCheck = new NullPresenceCheck( previousPropertyName, true ); + } + } + } + + String safeName = Strings.getSafeVariableName( propertyEntry.getName(), existingVariableNames ); + String source = previousPropertyName + "." + propertyEntry.getReadAccessor().getReadValueSource(); + safePropertyEntries.add( new SafePropertyEntry( + propertyEntry.getType(), + safeName, + source, + presenceCheck + ) ); existingVariableNames.add( safeName ); + thrownTypes.addAll( ctx.getTypeFactory().getThrownTypes( + propertyEntry.getReadAccessor() ) ); + previousPropertyName = safeName; + previousPropertyType = propertyEntry.getType(); + previousEntryIsNonNull = currentEntryIsNonNull; } + method.addThrownTypes( thrownTypes ); return new NestedPropertyMappingMethod( method, safePropertyEntries ); } + + private PresenceCheck getPresenceCheck(PropertyEntry propertyEntry, String previousPropertyName) { + PresenceCheckAccessor propertyPresenceChecker = propertyEntry.getPresenceChecker(); + if ( propertyPresenceChecker != null ) { + return new SuffixPresenceCheck( + previousPropertyName, + propertyPresenceChecker.getPresenceCheckSuffix(), + true + ); + } + return null; + } } - private NestedPropertyMappingMethod( Method method, List sourcePropertyEntries ) { + private NestedPropertyMappingMethod( ForgedMethod method, List sourcePropertyEntries ) { super( method ); this.safePropertyEntries = sourcePropertyEntries; } public Parameter getSourceParameter() { for ( Parameter parameter : getParameters() ) { - if ( !parameter.isMappingTarget() ) { + if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { return parameter; } } @@ -92,8 +187,11 @@ public List getPropertyEntries() { @Override public Set getImportTypes() { Set types = super.getImportTypes(); - for ( SafePropertyEntry propertyEntry : safePropertyEntries) { - types.add( propertyEntry.getType() ); + for ( SafePropertyEntry propertyEntry : safePropertyEntries ) { + types.addAll( propertyEntry.getType().getImportTypes() ); + if ( propertyEntry.getPresenceChecker() != null ) { + types.addAll( propertyEntry.getPresenceChecker().getImportTypes() ); + } } return types; } @@ -117,9 +215,10 @@ public boolean equals( Object obj ) { if ( getClass() != obj.getClass() ) { return false; } + NestedPropertyMappingMethod other = (NestedPropertyMappingMethod) obj; - if ( !getReturnType().equals( other.getReturnType() ) ) { + if ( !super.equals( obj ) ) { return false; } @@ -133,30 +232,76 @@ public boolean equals( Object obj ) { } } - if ( !getName().equals( other.getName() ) ) { + if ( !safePropertyEntries.equals( other.safePropertyEntries ) ) { return false; } return true; } - public static class SafePropertyEntry extends PropertyEntry { + public static class SafePropertyEntry { private final String safeName; + private final String source; + private final PresenceCheck presenceChecker; + private final Type type; - public SafePropertyEntry( PropertyEntry entry, String safeName ) { - super( entry.getName(), entry.getAccessor(), entry.getType() ); + public SafePropertyEntry(Type type, String safeName, String source, PresenceCheck presenceCheck) { this.safeName = safeName; + this.source = source; + this.presenceChecker = presenceCheck; + this.type = type; } - @Override public String getName() { return safeName; } - public String getAccessorName() { - return getAccessor().getSimpleName().toString(); + public String getSource() { + return source; } - } + public PresenceCheck getPresenceChecker() { + return presenceChecker; + } + + public Type getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof SafePropertyEntry ) ) { + return false; + } + + SafePropertyEntry that = (SafePropertyEntry) o; + + if ( !Objects.equals( source, that.source ) ) { + return false; + } + + if ( !Objects.equals( presenceChecker, that.presenceChecker ) ) { + return false; + } + + if ( !Objects.equals( type, that.type ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = source != null ? source.hashCode() : 0; + result = 31 * result + ( presenceChecker != null ? presenceChecker.hashCode() : 0 ); + result = 31 * result + ( type != null ? type.hashCode() : 0 ); + return result; + } + } } + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java new file mode 100644 index 0000000000..83464967e6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -0,0 +1,813 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import org.mapstruct.ap.internal.model.beanmapping.MappingReference; +import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; +import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry; +import org.mapstruct.ap.internal.model.beanmapping.SourceReference; +import org.mapstruct.ap.internal.model.beanmapping.TargetReference; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.MappingOptions; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * This is a helper class that holds the generated {@link PropertyMapping}(s) and all the information associated with + * it for nested target properties. + * + * @author Filip Hrisafov + */ +public class NestedTargetPropertyMappingHolder { + + private final List processedSourceParameters; + private final Set handledTargets; + private final List propertyMappings; + private final Map> unprocessedDefinedTarget; + private final boolean errorOccurred; + + public NestedTargetPropertyMappingHolder( + List processedSourceParameters, Set handledTargets, + List propertyMappings, + Map> unprocessedDefinedTarget, boolean errorOccurred) { + this.processedSourceParameters = processedSourceParameters; + this.handledTargets = handledTargets; + this.propertyMappings = propertyMappings; + this.unprocessedDefinedTarget = unprocessedDefinedTarget; + this.errorOccurred = errorOccurred; + } + + /** + * @return The source parameters that were processed during the generation of the property mappings + */ + public List getProcessedSourceParameters() { + return processedSourceParameters; + } + + /** + * @return all the targets that were handled + */ + public Set getHandledTargets() { + return handledTargets; + } + + /** + * @return the generated property mappings + */ + public List getPropertyMappings() { + return propertyMappings; + } + + /** + * @return a map of all the unprocessed defined targets that can be applied to name forged base methods + */ + public Map> getUnprocessedDefinedTarget() { + return unprocessedDefinedTarget; + } + + /** + * @return {@code true} if an error occurred during the creation of the nested mappings + */ + public boolean hasErrorOccurred() { + return errorOccurred; + } + + public static class Builder { + + private Method method; + private MappingReferences mappingReferences; + private MappingBuilderContext mappingContext; + private Set existingVariableNames; + private List propertyMappings; + private Set handledTargets; + private Map targetPropertiesWriteAccessors; + private Type targetType; + private boolean errorOccurred; + + public Builder mappingReferences(MappingReferences mappingReferences) { + this.mappingReferences = mappingReferences; + return this; + } + + public Builder method(Method method) { + this.method = method; + return this; + } + + public Builder mappingContext(MappingBuilderContext mappingContext) { + this.mappingContext = mappingContext; + return this; + } + + public Builder existingVariableNames(Set existingVariableNames) { + this.existingVariableNames = existingVariableNames; + return this; + } + + public Builder targetPropertiesWriteAccessors(Map targetPropertiesWriteAccessors) { + this.targetPropertiesWriteAccessors = targetPropertiesWriteAccessors; + return this; + } + + public Builder targetPropertyType(Type targetType) { + this.targetType = targetType; + return this; + } + + public NestedTargetPropertyMappingHolder build() { + List processedSourceParameters = new ArrayList<>(); + handledTargets = new HashSet<>(); + propertyMappings = new ArrayList<>(); + + // first we group by the first property in the target properties and for each of those + // properties we get the new mappings as if the first property did not exist. + GroupedTargetReferences groupedByTP = groupByTargetReferences( ); + Map> unprocessedDefinedTarget = new LinkedHashMap<>(); + + for ( Map.Entry> entryByTP : + groupedByTP.poppedTargetReferences.entrySet() ) { + String targetProperty = entryByTP.getKey(); + //Now we are grouping the already popped mappings by the source parameter(s) of the method + GroupedBySourceParameters groupedBySourceParam = groupBySourceParameter( + entryByTP.getValue(), + groupedByTP.singleTargetReferences.get( targetProperty ) + ); + boolean multipleSourceParametersForTP = groupedBySourceParam.groupedBySourceParameter.size() > 1; + + // All not processed mappings that should have been applied to all are part of the unprocessed + // defined targets + unprocessedDefinedTarget.put( targetProperty, groupedBySourceParam.notProcessedAppliesToAll ); + for ( Map.Entry> entryByParam : groupedBySourceParam + .groupedBySourceParameter.entrySet() ) { + + Parameter sourceParameter = entryByParam.getKey(); + + // Lastly we need to group by the source references. This will allow us to actually create + // the next mappings by popping source elements + GroupedSourceReferences groupedSourceReferences = groupByPoppedSourceReferences( + entryByParam, + groupedByTP.singleTargetReferences.get( targetProperty ) + ); + + // We need an update method in the when one of the following is satisfied: + // 1) Multiple source parameters for the target reference + // 2) Multiple source references for the target reference + // The reason for this is that multiple sources have effect on the target. + // See Issue1828Test for more info. + boolean forceUpdateMethod = + multipleSourceParametersForTP || groupedSourceReferences.groupedBySourceReferences.size() > 1; + + boolean forceUpdateMethodOrNonNestedReferencesPresent = + forceUpdateMethod || !groupedSourceReferences.nonNested.isEmpty(); + // For all the groupedBySourceReferences we need to create property mappings + // from the Mappings and not restrict on the defined mappings (allow to forge name based mapping) + // if we have composite methods i.e. more then 2 parameters then we have to force a creation + // of an update method in our generation + for ( Map.Entry> entryBySP : groupedSourceReferences + .groupedBySourceReferences + .entrySet() ) { + PropertyEntry sourceEntry = entryBySP.getKey(); + // If there are multiple source parameters that are mapped to the target reference + // then we restrict the mapping only to the defined mappings. And we create MappingOptions + // for forged methods (which means that any unmapped target properties are ignored) + // MappingOptions for forged methods is also created if we have something like this: + //@Mappings({ + // @Mapping(target = "vehicleInfo", source = "vehicleTypeInfo"), + // @Mapping(target = "vehicleInfo.images", source = "images") + //}) + // See Issue1269Test, Issue1247Test, AutomappingAndNestedTest for more info as well + MappingReferences sourceMappingRefs = new MappingReferences( entryBySP.getValue(), + multipleSourceParametersForTP, + forceUpdateMethodOrNonNestedReferencesPresent + ); + + SourceReference sourceRef = new SourceReference.BuilderFromProperty() + .sourceParameter( sourceParameter ) + .type( sourceEntry.getType() ) + .readAccessor( sourceEntry.getReadAccessor() ) + .presenceChecker( sourceEntry.getPresenceChecker() ) + .name( targetProperty ) + .build(); + + // If we have multiple source parameters that are mapped to the target reference, or + // parts of the nested properties are mapped to different sources (see comment above as well) + // we would force an update method + PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( + sourceMappingRefs, + targetProperty, + sourceRef, + forceUpdateMethodOrNonNestedReferencesPresent + ); + + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); + } + + handledTargets.add( entryByTP.getKey() ); + } + + // For the nonNested mappings (asymmetric) Mappings we also forge mappings + // However, here we do not forge name based mappings and we only + // do update on the defined Mappings. + if ( !groupedSourceReferences.nonNested.isEmpty() ) { + MappingReferences mappingReferences = + new MappingReferences( groupedSourceReferences.nonNested, true ); + SourceReference reference = new SourceReference.BuilderFromProperty() + .sourceParameter( sourceParameter ) + .name( targetProperty ) + .build(); + + boolean forceUpdateMethodForNonNested = + multipleSourceParametersForTP || + !groupedSourceReferences.groupedBySourceReferences.isEmpty(); + // If an update method is forced or there are groupedBySourceReferences then we must create + // an update method. The reason is that they might be for the same reference and we should + // use update for it + PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( + mappingReferences, + targetProperty, + reference, + forceUpdateMethodForNonNested + ); + + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); + } + + handledTargets.add( entryByTP.getKey() ); + } + + handleSourceParameterMappings( + groupedSourceReferences.sourceParameterMappings, + targetProperty, + sourceParameter, + multipleSourceParametersForTP + ); + + unprocessedDefinedTarget.put( targetProperty, groupedSourceReferences.notProcessedAppliesToAll ); + } + } + + return new NestedTargetPropertyMappingHolder( + processedSourceParameters, + handledTargets, + propertyMappings, + unprocessedDefinedTarget, + errorOccurred + ); + } + + /** + * Handle the {@link PropertyMapping} creation for source parameter mappings. + * + * @param sourceParameterMappings the source parameter mappings for which property mapping should be done + * @param targetProperty the target property that is being mapped + * @param sourceParameter the source parameter that is used + * @param forceUpdateMethod whether we need to force an update method + */ + private void handleSourceParameterMappings(Set sourceParameterMappings, + String targetProperty, Parameter sourceParameter, + boolean forceUpdateMethod) { + if ( !sourceParameterMappings.isEmpty() ) { + // The source parameter mappings have no mappings, the source name is actually the parameter itself + MappingReferences nonNestedRefs = + new MappingReferences( Collections.emptySet(), false, true ); + SourceReference reference = new SourceReference.BuilderFromProperty() + .sourceParameter( sourceParameter ) + .name( targetProperty ) + .build(); + + PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( + nonNestedRefs, + targetProperty, + reference, + forceUpdateMethod + ); + + if ( propertyMapping != null ) { + propertyMappings.add( propertyMapping ); + } + + handledTargets.add( targetProperty ); + } + } + + /** + * The target references are popped. The {@code List<}{@link MappingOptions}{@code >} are keyed on the unique + * first entries of the target references. + * + *

    + *

    + * Group all target references by their first property and for each such mapping use a new one where the + * first property will be removed from it. If a {@link org.mapstruct.Mapping} cannot be popped, i.e. it + * contains a non nested target property just keep it as is (this is usually needed to control how an + * intermediary level can be mapped). + * + *

    + *

    + * We start with the following mappings: + * + *

    +         * {@literal @}Mapping(target = "fish.kind", source = "fish.type"),
    +         * {@literal @}Mapping(target = "fish.name", ignore = true),
    +         * {@literal @}Mapping(target = "ornament", ignore = true ),
    +         * {@literal @}Mapping(target = "material.materialType", source = "material"),
    +         * {@literal @}Mapping(target = "document", source = "report"),
    +         * {@literal @}Mapping(target = "document.organisation.name", source = "report.organisationName")
    +         * 
    + * + * We will get this: + * + *
    +         * // All target references are popped and grouped by their first property
    +         * GroupedTargetReferences.poppedTargetReferences {
    +         *     fish:     {@literal @}Mapping(target = "kind", source = "fish.type"),
    +         *               {@literal @}Mapping(target = "name", ignore = true);
    +         *     material: {@literal @}Mapping(target = "materialType", source = "material");
    +         *     document: {@literal @}Mapping(target = "organization.name", source = "report.organizationName");
    +         * }
    +         *
    +         * //This references are not nested and we they stay as they were
    +         * GroupedTargetReferences.singleTargetReferences {
    +         *     document: {@literal @}Mapping(target = "document", source = "report");
    +         *     ornament: {@literal @}Mapping(target = "ornament", ignore = true );
    +         * }
    +         * 
    + * + * @return See above + */ + private GroupedTargetReferences groupByTargetReferences( ) { + // group all mappings based on the top level name before popping + Map> mappingsKeyedByProperty = new LinkedHashMap<>(); + Map> singleTargetReferences = new LinkedHashMap<>(); + for ( MappingReference mapping : mappingReferences.getMappingReferences() ) { + TargetReference targetReference = mapping.getTargetReference(); + List propertyEntries = targetReference.getPropertyEntries(); + if ( propertyEntries.isEmpty() ) { + // This can happen if the target property is target = ".", + // this usually happens when doing a reverse mapping + continue; + } + String property = first( propertyEntries ); + MappingReference newMapping = mapping.popTargetReference(); + if ( newMapping != null ) { + // group properties on current name. + mappingsKeyedByProperty.computeIfAbsent( property, propertyEntry -> new LinkedHashSet<>() ) + .add( newMapping ); + } + else { + singleTargetReferences.computeIfAbsent( property, propertyEntry -> new LinkedHashSet<>() ) + .add( mapping ); + } + } + + return new GroupedTargetReferences( mappingsKeyedByProperty, singleTargetReferences ); + } + + /** + * Splits the List of Mappings into possibly more Mappings based on each source method parameter type. + * + * Note: this method is used for forging nested update methods. For that purpose, the same method with all + * joined mappings should be generated. See also: NestedTargetPropertiesTest#shouldMapNestedComposedTarget + * + * Mappings: + *
    +         * {@literal @}Mapping(target = "organization.name", source = "report.organizationName");
    +         * 
    + * + * singleTargetReferences: + *
    +         * {@literal @}Mapping(target = "document", source = "report");
    +         * 
    + * + * We assume that all properties belong to the same source parameter (fish). We are getting this in return: + * + *
    +         * GroupedBySourceParameters.groupedBySourceParameter {
    +         *     fish: {@literal @}Mapping(target = "organization.name", source = "report.organizationName");
    +         * }
    +         *
    +         * GroupedBySourceParameters.notProcessedAppliesToAll {} //empty
    +         *
    +         * 
    + * + * Notice how the {@code singleTargetReferences} are missing. They are used for situations when there are + * mappings without source. Such as: + * Mappings: + *
    +         * {@literal @}Mapping(target = "organization.name", expression="java(\"Dunno\")");
    +         * 
    + * + * singleTargetReferences: + *
    +         * {@literal @}Mapping(target = "document", source = "report");
    +         * 
    + * + * The mappings have no source reference so we cannot extract the source parameter from them. When mappings + * have no source properties then we apply those to all of them. In this case we have a single target + * reference that can provide a source parameter. So we will get: + *
    +         * GroupedBySourceParameters.groupedBySourceParameter {
    +         *     fish: {@literal @}Mapping(target = "organization.name", expression="java(\"Dunno\")");
    +         * }
    +         *
    +         * GroupedBySourceParameters.notProcessedAppliesToAll {} //empty
    +         * 
    + * + *

    + * See also how the {@code singleTargetReferences} are not part of the mappings. They are used only to + * make sure that their source parameter is taken into consideration in the next step. + * + *

    + * The {@code notProcessedAppliesToAll} contains all Mappings that should have been applied to all but have not + * because there were no other mappings that we could have used to pass them along. These + * {@link org.mapstruct.Mapping}(s) are used later on for normal mapping. + * + *

    + * If we only had: + *

    +         * {@literal @}Mapping(target = "document", source = "report");
    +         * {@literal @}Mapping(target = "ornament", ignore = true );
    +         * 
    + * + * Then we only would have had: + *
    +         * GroupedBySourceParameters.notProcessedAppliesToAll {
    +         * {@literal @}Mapping(target = "document", source = "report");
    +         * {@literal @}Mapping(target = "ornament", ignore = true );
    +         * }
    +         * 
    + * + * These mappings will be part of the {@code GroupedBySourceParameters.notProcessedAppliesToAll} and are + * used to be passed to the normal defined mapping. + * + * + * @param mappings that mappings that need to be used for the grouping + * @param singleTargetReferences a List containing all non-nested mappings for the same grouped target + * property as the {@code mappings} + * @return the split mapping options. + */ + private GroupedBySourceParameters groupBySourceParameter(Set mappings, + Set singleTargetReferences) { + + Map> mappingsKeyedByParameter = new LinkedHashMap<>(); + Set appliesToAll = new LinkedHashSet<>(); + for ( MappingReference mapping : mappings ) { + if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) { + Parameter parameter = mapping.getSourceReference().getParameter(); + mappingsKeyedByParameter.computeIfAbsent( parameter, key -> new LinkedHashSet<>() ) + .add( mapping ); + } + else { + appliesToAll.add( mapping ); + } + } + + populateWithSingleTargetReferences( + mappingsKeyedByParameter, + singleTargetReferences, + SourceReference::getParameter + ); + + for ( Map.Entry> entry : mappingsKeyedByParameter.entrySet() ) { + entry.getValue().addAll( appliesToAll ); + } + + Set notProcessAppliesToAll = + mappingsKeyedByParameter.isEmpty() ? appliesToAll : new LinkedHashSet<>(); + + return new GroupedBySourceParameters( mappingsKeyedByParameter, notProcessAppliesToAll ); + } + + /** + * Creates a nested grouping by popping the source mappings. See the description of the class to see what is + * generated. + * + * Mappings: + *
    +         * {@literal @}Mapping(target = "organization.name", source = "report.organizationName");
    +         * 
    + * + * singleTargetReferences: + *
    +         * {@literal @}Mapping(target = "document", source = "report");
    +         * 
    + * + * And we get: + * + *
    +         * GroupedSourceReferences.groupedBySourceReferences {
    +         *     report: {@literal @}Mapping(target = "organization.name", source = "organizationName");
    +         * }
    +         * 
    + * + * + * + * @param entryByParam the entry of a {@link Parameter} and it's associated {@link MappingOptions}(s) that need + * to be used for grouping on popped source references + * @param singleTargetReferences the single target references that match the source mappings + * + * @return the Grouped Source References + */ + private GroupedSourceReferences groupByPoppedSourceReferences( + Map.Entry> entryByParam, + Set singleTargetReferences) { + Set mappings = entryByParam.getValue(); + Set nonNested = new LinkedHashSet<>(); + Set appliesToAll = new LinkedHashSet<>(); + Set sourceParameterMappings = new LinkedHashSet<>(); + // group all mappings based on the top level name before popping + Map> mappingsKeyedByProperty + = new LinkedHashMap<>(); + for ( MappingReference mapping : mappings ) { + MappingReference newMapping = mapping.popSourceReference(); + if ( newMapping != null ) { + // group properties on current name. + PropertyEntry property = first( mapping.getSourceReference().getPropertyEntries() ); + mappingsKeyedByProperty.computeIfAbsent( property, propertyEntry -> new LinkedHashSet<>() ) + .add( newMapping ); + } + //This is an ignore, or some expression, or a default. We apply these to all + else if ( mapping.getSourceReference() == null ) { + appliesToAll.add( mapping ); + } + else { + nonNested.add( mapping ); + } + } + + // We consider that there were no mappings if there are no mappingsKeyedByProperty + // and no nonNested. appliesToAll Mappings are mappings that have no source reference and need to be + // applied to everything. + boolean hasNoMappings = mappingsKeyedByProperty.isEmpty() && nonNested.isEmpty(); + Parameter sourceParameter = entryByParam.getKey(); + Set singleTargetReferencesToUse = + extractSingleTargetReferencesToUseAndPopulateSourceParameterMappings( + singleTargetReferences, + sourceParameterMappings, + hasNoMappings, + sourceParameter + ); + + populateWithSingleTargetReferences( + mappingsKeyedByProperty, + singleTargetReferencesToUse, + sourceReference -> sourceReference.getPropertyEntries().isEmpty() ? null : + first( sourceReference.getPropertyEntries() ) + ); + + for ( Map.Entry> entry : mappingsKeyedByProperty.entrySet() ) { + entry.getValue().addAll( appliesToAll ); + } + + Set notProcessedAppliesToAll = new LinkedHashSet<>(); + // If the applied to all were not added to all properties because they were empty, and the non-nested + // one are not empty, add them to the non-nested ones + if ( mappingsKeyedByProperty.isEmpty() && !nonNested.isEmpty() ) { + nonNested.addAll( appliesToAll ); + } + // Otherwise if the non-nested are empty, just pass them along so they can be processed later on + else if ( mappingsKeyedByProperty.isEmpty() && nonNested.isEmpty() ) { + notProcessedAppliesToAll.addAll( appliesToAll ); + } + + return new GroupedSourceReferences( + mappingsKeyedByProperty, + nonNested, + notProcessedAppliesToAll, + sourceParameterMappings + ); + } + + /** + * Extracts all relevant single target references and populates the {@code sourceParameterMappings} if needed. + * A relevant single target reference mapping is a mapping that has a valid source reference and is for + * the {@code sourceParameter}. If there are no mappings i.e. {@code hasNoMappings = true} and the source + * reference in the mapping has no property entries then add that to the {@code sourceParameterMappings} + * (mappings like this have found themselves here because there is a mapping method with multiple parameters + * and that are using the same sub-path in the target properties). + * + * @param singleTargetReferences All the single target references for a target property + * @param sourceParameterMappings a List that needs to be populated with valid mappings when {@code + * hasNoMappings = true} and there are no property entries in the source reference + * @param hasNoMappings parameter indicating whether there were any extracted mappings for this target property + * @param sourceParameter the source parameter for which the grouping is being done + * + * @return a set with valid single target references + */ + private Set extractSingleTargetReferencesToUseAndPopulateSourceParameterMappings( + Set singleTargetReferences, Set sourceParameterMappings, + boolean hasNoMappings, Parameter sourceParameter) { + Set singleTargetReferencesToUse = null; + if ( singleTargetReferences != null ) { + singleTargetReferencesToUse = new LinkedHashSet<>( singleTargetReferences.size() ); + for ( MappingReference mapping : singleTargetReferences ) { + if ( mapping.getSourceReference() == null || !mapping.getSourceReference().isValid() || + !sourceParameter.equals( mapping.getSourceReference().getParameter() ) ) { + // If the mapping has no sourceReference, it is not valid or it does not have the same source + // parameter then we need to ignore it. When a mapping method has multiple parameters it can + // happen that different parameters somehow have same path in the nesting + continue; + } + if ( hasNoMappings && mapping.getSourceReference().getPropertyEntries().isEmpty() ) { + // If there were no mappings for this source parameter and there are no property entries + // that means that this could be for a mapping method with multiple parameters. + // We have to consider and map this separately + sourceParameterMappings.add( mapping ); + } + else { + singleTargetReferencesToUse.add( mapping ); + } + } + } + return singleTargetReferencesToUse; + } + + private PropertyMapping createPropertyMappingForNestedTarget(MappingReferences mappingReferences, + String targetPropertyName, + SourceReference sourceReference, + boolean forceUpdateMethod) { + + Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName ); + ReadAccessor targetReadAccessor = targetType.getReadAccessor( + targetPropertyName, + method.getSourceParameters().size() == 1 + ); + if ( targetWriteAccessor == null ) { + Set readAccessors = targetType.getPropertyReadAccessors().keySet(); + String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); + + for ( MappingReference mappingReference : mappingReferences.getMappingReferences() ) { + MappingOptions mapping = mappingReference.getMapping(); + List pathProperties = new ArrayList<>( mappingReference.getTargetReference() + .getPathProperties() ); + if ( !pathProperties.isEmpty() ) { + pathProperties.set( pathProperties.size() - 1, mostSimilarProperty ); + } + + mappingContext.getMessager() + .printMessage( + mapping.getElement(), + mapping.getMirror(), + mapping.getTargetAnnotationValue(), + Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE, + targetPropertyName, + targetType.describe(), + mapping.getTargetName(), + Strings.join( pathProperties, "." ) + ); + } + + errorOccurred = true; + return null; + } + + return new PropertyMapping.PropertyMappingBuilder() + .mappingContext( mappingContext ) + .sourceMethod( method ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) + .sourceReference( sourceReference ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mappingReferences.collectNestedDependsOn() ) + .forgeMethodWithMappingReferences( mappingReferences ) + .forceUpdateMethod( forceUpdateMethod ) + .forgedNamedBased( false ) + .options( method.getOptions().getBeanMapping() ) + .build(); + } + + /** + * If a single target mapping has a valid {@link SourceReference} and the {@link SourceReference} has more + * then 0 {@link PropertyEntry} and if the {@code map} does not contain an entry with the extracted key then + * an entry with the extracted key and an empty list is added. + * + * @param map that needs to be populated + * @param singleTargetReferences to use + * @param keyExtractor to be used to extract a key + */ + private void populateWithSingleTargetReferences(Map> map, + Set singleTargetReferences, Function keyExtractor) { + if ( singleTargetReferences != null ) { + //This are non nested target references only their property needs to be added as they most probably + // define it + for ( MappingReference mapping : singleTargetReferences ) { + if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) { + K key = keyExtractor.apply( mapping.getSourceReference() ); + if ( key != null ) { + map.computeIfAbsent( key, keyValue -> new LinkedHashSet<>() ); + } + } + } + } + } + } + + private static class GroupedBySourceParameters { + private final Map> groupedBySourceParameter; + private final Set notProcessedAppliesToAll; + + private GroupedBySourceParameters(Map> groupedBySourceParameter, + Set notProcessedAppliesToAll) { + this.groupedBySourceParameter = groupedBySourceParameter; + this.notProcessedAppliesToAll = notProcessedAppliesToAll; + } + } + + /** + * The grouped target references. This class contains the poppedTarget references and the single target + * references (target references that were not nested). + */ + private static class GroupedTargetReferences { + private final Map> poppedTargetReferences; + private final Map> singleTargetReferences; + + private GroupedTargetReferences(Map> poppedTargetReferences, + Map> singleTargetReferences) { + this.poppedTargetReferences = poppedTargetReferences; + this.singleTargetReferences = singleTargetReferences; + } + + @Override + public String toString() { + return "GroupedTargetReferences{" + "poppedTargetReferences=" + poppedTargetReferences + + ", singleTargetReferences=" + singleTargetReferences + "}"; + } + } + + /** + * This class is used to group Source references in respected to the nesting that they have. + * + * This class contains all groupings by Property Entries if they are nested, or a list of all the other options + * that could not have been popped. + * + * So, take + * + * sourceReference 1: propertyEntryX.propertyEntryX1.propertyEntryX1a + * sourceReference 2: propertyEntryX.propertyEntryX2 + * sourceReference 3: propertyEntryY.propertyY1 + * sourceReference 4: propertyEntryZ + * sourceReference 5: propertyEntryZ2 + * + * We will have a map with entries: + *
    +     *
    +     * propertyEntryX - ( sourceReference1: propertyEntryX1.propertyEntryX1a,
    +     *                    sourceReference2 propertyEntryX2 )
    +     * propertyEntryY - ( sourceReference1: propertyEntryY1 )
    +     *
    +     * 
    + * + * If non-nested mappings were found they will be located in a List. + *
    +     * sourceReference 4: propertyEntryZ
    +     * sourceReference 5: propertyEntryZ2
    +     * 
    + * + *

    + * If Mappings that should apply to all were found, but no grouping was found, they will be located in a + * different list: + */ + private static class GroupedSourceReferences { + + private final Map> groupedBySourceReferences; + private final Set nonNested; + private final Set notProcessedAppliesToAll; + private final Set sourceParameterMappings; + + private GroupedSourceReferences(Map> groupedBySourceReferences, + Set nonNested, Set notProcessedAppliesToAll, + Set sourceParameterMappings) { + this.groupedBySourceReferences = groupedBySourceReferences; + this.nonNested = nonNested; + this.notProcessedAppliesToAll = notProcessedAppliesToAll; + this.sourceParameterMappings = sourceParameterMappings; + + } + + @Override + public String toString() { + return "GroupedSourceReferences{" + "groupedBySourceReferences=" + groupedBySourceReferences + + ", nonNested=" + nonNested + ", notProcessedAppliesToAll=" + notProcessedAppliesToAll + + ", sourceParameterMappings=" + sourceParameterMappings + '}'; + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NoArgumentConstructor.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NoArgumentConstructor.java new file mode 100644 index 0000000000..1101c15891 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NoArgumentConstructor.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.ap.internal.model; + +import java.util.Collections; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Represents a constructor that is used for constructor injection. + * + * @author Sjaak Derksen + */ +public class NoArgumentConstructor extends ModelElement implements Constructor { + + private final String name; + private final Set fragments; + + public NoArgumentConstructor(String name, Set fragments) { + this.name = name; + this.fragments = fragments; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + @Override + public String getName() { + return name; + } + + public Set getFragments() { + return fragments; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java new file mode 100644 index 0000000000..89ee1baab2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.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.ap.internal.model; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * A {@link MappingMethod} that is used by the main mapping methods ({@link BeanMappingMethod}, + * {@link MapMappingMethod}, {@link IterableMappingMethod} and {@link StreamMappingMethod} (non-enum / non-value + * mapping) + * + * @author Filip Hrisafov + */ +public abstract class NormalTypeMappingMethod extends MappingMethod { + private final MethodReference factoryMethod; + private final boolean mapNullToDefault; + + private final List annotations; + + NormalTypeMappingMethod(Method method, List annotations, + Collection existingVariableNames, MethodReference factoryMethod, + boolean mapNullToDefault, + List beforeMappingReferences, + List afterMappingReferences) { + super( method, existingVariableNames, beforeMappingReferences, afterMappingReferences ); + this.factoryMethod = factoryMethod; + this.mapNullToDefault = mapNullToDefault; + this.annotations = annotations; + } + + @Override + public Set getImportTypes() { + Set types = super.getImportTypes(); + if ( ( factoryMethod == null ) && ( !isExistingInstanceMapping() ) ) { + if ( getReturnType().getImplementationType() != null ) { + types.addAll( getReturnType().getImplementationType().getImportTypes() ); + } + } + else if ( factoryMethod != null ) { + types.addAll( factoryMethod.getImportTypes() ); + } + for ( Annotation annotation : annotations ) { + types.addAll( annotation.getImportTypes() ); + } + return types; + } + + public boolean isMapNullToDefault() { + return mapNullToDefault; + } + + public MethodReference getFactoryMethod() { + return this.factoryMethod; + } + + public List getAnnotations() { + return annotations; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ( ( getResultType() == null ) ? 0 : getResultType().hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + NormalTypeMappingMethod other = (NormalTypeMappingMethod) obj; + + if ( !super.equals( obj ) ) { + return false; + } + + if ( !getResultType().equals( other.getResultType() ) ) { + return false; + } + + if ( getSourceParameters().size() != other.getSourceParameters().size() ) { + return false; + } + + for ( int i = 0; i < getSourceParameters().size(); i++ ) { + if ( !getSourceParameters().get( i ).getType().equals( other.getSourceParameters().get( i ).getType() ) ) { + return false; + } + List thisTypeParameters = getSourceParameters().get( i ).getType().getTypeParameters(); + List otherTypeParameters = other.getSourceParameters().get( i ).getType().getTypeParameters(); + + if ( !thisTypeParameters.equals( otherTypeParameters ) ) { + return false; + } + } + + return isMapNullToDefault() == other.isMapNullToDefault(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java new file mode 100644 index 0000000000..2d7da03abb --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -0,0 +1,190 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; + +import org.mapstruct.ap.internal.model.common.BuilderType; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; +import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; +import org.mapstruct.ap.internal.model.source.selector.SelectionContext; +import org.mapstruct.ap.internal.util.Message; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * Factory for creating the appropriate object factory method. + * + * @author Sjaak Derksen + */ +public class ObjectFactoryMethodResolver { + + private ObjectFactoryMethodResolver() { + } + + /** + * returns a no arg factory method + * + * @param method target mapping method + * @param selectionParameters parameters used in the selection process + * @param ctx the mapping builder context + * + * @return a method reference to the factory method, or null if no suitable, or ambiguous method found + */ + public static MethodReference getFactoryMethod( Method method, + SelectionParameters selectionParameters, + MappingBuilderContext ctx) { + return getFactoryMethod( method, method.getResultType(), selectionParameters, ctx ); + } + + /** + * returns a no arg factory method + * + * @param method target mapping method + * @param alternativeTarget alternative to {@link Method#getResultType()} e.g. when target is abstract + * @param selectionParameters parameters used in the selection process + * @param ctx the mapping builder context + * + * @return a method reference to the factory method, or null if no suitable, or ambiguous method found + * + */ + public static MethodReference getFactoryMethod( Method method, + Type alternativeTarget, + SelectionParameters selectionParameters, + MappingBuilderContext ctx) { + + + List> matchingFactoryMethods = getMatchingFactoryMethods( + method, + alternativeTarget, + selectionParameters, + ctx + ); + + if ( matchingFactoryMethods.isEmpty() ) { + return null; + } + + if ( matchingFactoryMethods.size() > 1 ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.GENERAL_AMBIGUOUS_FACTORY_METHOD, + alternativeTarget.describe(), + matchingFactoryMethods.stream() + .map( SelectedMethod::getMethod ) + .map( Method::describe ) + .collect( Collectors.joining( ", " ) ) + ); + + return null; + } + + SelectedMethod matchingFactoryMethod = first( matchingFactoryMethods ); + + return getFactoryMethodReference( method, matchingFactoryMethod, ctx ); + } + + public static MethodReference getFactoryMethodReference(Method method, + SelectedMethod matchingFactoryMethod, MappingBuilderContext ctx) { + Parameter providingParameter = + method.getContextProvidedMethods().getParameterForProvidedMethod( matchingFactoryMethod.getMethod() ); + + if ( providingParameter != null ) { + return MethodReference.forParameterProvidedMethod( + matchingFactoryMethod.getMethod(), + providingParameter, + matchingFactoryMethod.getParameterBindings() ); + } + else { + MapperReference ref = MapperReference.findMapperReference( + ctx.getMapperReferences(), + matchingFactoryMethod.getMethod() ); + + return MethodReference.forMapperReference( + matchingFactoryMethod.getMethod(), + ref, + matchingFactoryMethod.getParameterBindings() ); + } + } + + public static List> getMatchingFactoryMethods( Method method, + Type alternativeTarget, + SelectionParameters selectionParameters, + MappingBuilderContext ctx) { + + MethodSelectors selectors = + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), null ); + + return selectors.getMatchingMethods( + getAllAvailableMethods( method, ctx.getSourceModel() ), + SelectionContext.forFactoryMethods( method, alternativeTarget, selectionParameters, ctx.getTypeFactory() ) + ); + } + + public static MethodReference getBuilderFactoryMethod(Method method, BuilderType builder ) { + Type returnType = method.getReturnType(); + if ( returnType.isOptionalType() ) { + returnType = returnType.getOptionalBaseType(); + } + return getBuilderFactoryMethod( returnType, builder ); + } + + public static MethodReference getBuilderFactoryMethod(Type typeToBuild, BuilderType builder ) { + if ( builder == null ) { + return null; + } + + ExecutableElement builderCreationMethod = builder.getBuilderCreationMethod(); + if ( builderCreationMethod.getKind() == ElementKind.CONSTRUCTOR ) { + // If the builder creation method is a constructor it would be handled properly down the line + return null; + } + + if ( !builder.getBuildingType().isAssignableTo( typeToBuild ) ) { + //TODO print error message + return null; + } + + return MethodReference.forStaticBuilder( + builderCreationMethod.getSimpleName().toString(), + builder.getOwningType() + ); + } + + private static List getAllAvailableMethods(Method method, List sourceModelMethods) { + ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods(); + if ( contextProvidedMethods.isEmpty() ) { + return sourceModelMethods; + } + + List methodsProvidedByParams = contextProvidedMethods + .getAllProvidedMethodsInParameterOrder( method.getContextParameters() ); + + List availableMethods = + new ArrayList<>( methodsProvidedByParams.size() + sourceModelMethods.size() ); + + for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) { + // add only methods from context that do have the @ObjectFactory annotation + if ( methodProvidedByParams.hasObjectFactoryAnnotation() ) { + availableMethods.add( methodProvidedByParams ); + } + } + availableMethods.addAll( sourceModelMethods ); + + return availableMethods; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java new file mode 100644 index 0000000000..3163614871 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java @@ -0,0 +1,215 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; +import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; +import org.mapstruct.ap.internal.model.source.selector.SelectionContext; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.NullabilityResolver; + +/** + * Factory for creating {@link PresenceCheck}s. + * + * @author Filip Hrisafov + */ +public final class PresenceCheckMethodResolver { + + private PresenceCheckMethodResolver() { + + } + + public static PresenceCheck getPresenceCheck( + Method method, + SelectionParameters selectionParameters, + MappingBuilderContext ctx + ) { + List> matchingMethods = findMatchingMethods( + method, + SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() ), + ctx + ); + + if ( matchingMethods.isEmpty() ) { + return null; + } + + if ( matchingMethods.size() > 1 ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD, + selectionParameters.getSourceRHS().getSourceType().describe(), + matchingMethods.stream() + .map( SelectedMethod::getMethod ) + .map( Method::describe ) + .collect( Collectors.joining( ", " ) ) + ); + + return null; + } + + SelectedMethod matchingMethod = matchingMethods.get( 0 ); + + MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx ); + + return new MethodReferencePresenceCheck( methodReference ); + + } + + public static PresenceCheck getPresenceCheckForSourceParameter( + Method method, + SelectionParameters selectionParameters, + Parameter sourceParameter, + MappingBuilderContext ctx + ) { + List> matchingMethods = findMatchingMethods( + method, + SelectionContext.forSourceParameterPresenceCheckMethods( + method, + selectionParameters, + sourceParameter, + ctx.getTypeFactory() + ), + ctx + ); + + if ( matchingMethods.isEmpty() ) { + if ( sourceParameter.getType().isOptionalType() ) { + return new OptionalPresenceCheck( sourceParameter.getName(), ctx.getVersionInformation() ); + } + else if ( !sourceParameter.getType().isPrimitive() ) { + // If the source parameter is @NonNull (JSpecify), skip the null guard entirely. + // Resolved in the mapper's @NullMarked scope since the parameter is declared in the mapper interface. + if ( ctx.getNullabilityInMapperScope( sourceParameter.getElement() ) + == NullabilityResolver.Nullability.NON_NULL ) { + ctx.getMessager().note( 2, + Message.PROPERTYMAPPING_JSPECIFY_SKIP_METHOD_GUARD_NON_NULL_PARAM, + sourceParameter.getName() + ); + return null; + } + return new NullPresenceCheck( sourceParameter.getName() ); + } + return null; + } + + if ( matchingMethods.size() > 1 ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.GENERAL_AMBIGUOUS_SOURCE_PARAMETER_CHECK_METHOD, + sourceParameter.getType().describe(), + matchingMethods.stream() + .map( SelectedMethod::getMethod ) + .map( Method::describe ) + .collect( Collectors.joining( ", " ) ) + ); + + return null; + } + + SelectedMethod matchingMethod = matchingMethods.get( 0 ); + + MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx ); + + return new MethodReferencePresenceCheck( methodReference ); + + } + + private static List> findMatchingMethods( + Method method, + SelectionContext selectionContext, + MappingBuilderContext ctx + ) { + MethodSelectors selectors = new MethodSelectors( + ctx.getTypeUtils(), + ctx.getElementUtils(), + ctx.getMessager(), + null + ); + + return selectors.getMatchingMethods( + getAllAvailableMethods( method, ctx.getSourceModel(), selectionContext.getSelectionCriteria() ), + selectionContext + ); + } + + private static MethodReference getPresenceCheckMethodReference( + Method method, + SelectedMethod matchingMethod, + MappingBuilderContext ctx + ) { + + Parameter providingParameter = + method.getContextProvidedMethods().getParameterForProvidedMethod( matchingMethod.getMethod() ); + if ( providingParameter != null ) { + return MethodReference.forParameterProvidedMethod( + matchingMethod.getMethod(), + providingParameter, + matchingMethod.getParameterBindings() + ); + } + else { + MapperReference ref = MapperReference.findMapperReference( + ctx.getMapperReferences(), + matchingMethod.getMethod() + ); + + return MethodReference.forMapperReference( + matchingMethod.getMethod(), + ref, + matchingMethod.getParameterBindings() + ); + } + } + + private static List getAllAvailableMethods(Method method, List sourceModelMethods, + SelectionCriteria selectionCriteria) { + ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods(); + if ( contextProvidedMethods.isEmpty() ) { + return sourceModelMethods; + } + + List methodsProvidedByParams = contextProvidedMethods + .getAllProvidedMethodsInParameterOrder( method.getContextParameters() ); + + List availableMethods = + new ArrayList<>( methodsProvidedByParams.size() + sourceModelMethods.size() ); + + for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) { + if ( selectionCriteria.isPresenceCheckRequired() ) { + // add only methods from context that do have the @Condition for properties annotation + if ( methodProvidedByParams.getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) { + availableMethods.add( methodProvidedByParams ); + } + } + else if ( selectionCriteria.isSourceParameterCheckRequired() ) { + // add only methods from context that do have the @Condition for source parameters annotation + if ( methodProvidedByParams.getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) { + availableMethods.add( methodProvidedByParams ); + } + } + } + availableMethods.addAll( sourceModelMethods ); + + return availableMethods; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 0261a09e59..163c390b66 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -1,57 +1,70 @@ -/** - * Copyright 2012-2015 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.model; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Supplier; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; import org.mapstruct.ap.internal.model.assignment.AdderWrapper; import org.mapstruct.ap.internal.model.assignment.ArrayCopyWrapper; -import org.mapstruct.ap.internal.model.assignment.Assignment; +import org.mapstruct.ap.internal.model.assignment.EnumConstantWrapper; import org.mapstruct.ap.internal.model.assignment.GetterWrapperForCollectionsAndMaps; -import org.mapstruct.ap.internal.model.assignment.NewCollectionOrMapWrapper; -import org.mapstruct.ap.internal.model.assignment.NullCheckWrapper; import org.mapstruct.ap.internal.model.assignment.SetterWrapper; -import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMaps; -import org.mapstruct.ap.internal.model.assignment.UpdateNullCheckWrapper; +import org.mapstruct.ap.internal.model.assignment.StreamAdderWrapper; import org.mapstruct.ap.internal.model.assignment.UpdateWrapper; +import org.mapstruct.ap.internal.model.beanmapping.MappingReferences; +import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry; +import org.mapstruct.ap.internal.model.beanmapping.SourceReference; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.BuilderType; +import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.source.ForgedMethod; -import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.model.source.SourceReference; -import org.mapstruct.ap.internal.model.source.SourceReference.PropertyEntry; -import org.mapstruct.ap.internal.util.Executables; +import org.mapstruct.ap.internal.model.presence.AllPresenceChecksPresenceCheck; +import org.mapstruct.ap.internal.model.presence.JavaExpressionPresenceCheck; +import org.mapstruct.ap.internal.model.presence.NullPresenceCheck; +import org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck; +import org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck; +import org.mapstruct.ap.internal.model.source.DelegatingOptions; +import org.mapstruct.ap.internal.model.source.MappingControl; +import org.mapstruct.ap.internal.model.source.MappingOptions; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.NativeTypes; +import org.mapstruct.ap.internal.util.NullabilityResolver; import org.mapstruct.ap.internal.util.Strings; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeMirror; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; - -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.DIRECT; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.MAPPED_TYPE_CONVERTED; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED; -import static org.mapstruct.ap.internal.model.assignment.Assignment.AssignmentType.TYPE_CONVERTED_MAPPED; -import org.mapstruct.ap.internal.util.MapperConfiguration; +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; + +import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_NULL; +import static org.mapstruct.ap.internal.model.ForgedMethod.forElementMapping; +import static org.mapstruct.ap.internal.model.ForgedMethod.forParameterMapping; +import static org.mapstruct.ap.internal.model.ForgedMethod.forPropertyMapping; +import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT; /** * Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to @@ -63,51 +76,61 @@ public class PropertyMapping extends ModelElement { private final String name; + private final String sourcePropertyName; private final String sourceBeanName; private final String targetWriteAccessorName; - private final String targetReadAccessorName; + private final ReadAccessor targetReadAccessorProvider; private final Type targetType; private final Assignment assignment; - private final List dependsOn; + private final Set dependsOn; private final Assignment defaultValueAssignment; + private final boolean constructorMapping; @SuppressWarnings("unchecked") - private static class MappingBuilderBase> { + private static class MappingBuilderBase> extends AbstractBaseBuilder { - protected MappingBuilderContext ctx; - protected SourceMethod method; - protected ExecutableElement targetWriteAccessor; - protected ExecutableElement targetReadAccessor; + protected Accessor targetWriteAccessor; + protected AccessorType targetWriteAccessorType; + protected Type targetType; + protected BuilderType targetBuilderType; + protected ReadAccessor targetReadAccessor; protected String targetPropertyName; - protected List dependsOn; + protected String sourcePropertyName; + + protected Set dependsOn; protected Set existingVariableNames; + protected AnnotationMirror positionHint; - public T mappingContext(MappingBuilderContext mappingContext) { - this.ctx = mappingContext; - return (T) this; + MappingBuilderBase(Class selfType) { + super( selfType ); } - public T sourceMethod(SourceMethod sourceMethod) { - this.method = sourceMethod; - return (T) this; + public T sourceMethod(Method sourceMethod) { + return super.method( sourceMethod ); } - public T targetReadAccessor(ExecutableElement targetReadAccessor) { + public T target(String targetPropertyName, ReadAccessor targetReadAccessor, Accessor targetWriteAccessor) { + this.targetPropertyName = targetPropertyName; this.targetReadAccessor = targetReadAccessor; + this.targetWriteAccessor = targetWriteAccessor; + this.targetType = ctx.getTypeFactory().getType( targetWriteAccessor.getAccessedType() ); + BuilderGem builder = method.getOptions().getBeanMapping().getBuilder(); + this.targetBuilderType = ctx.getTypeFactory().builderTypeFor( this.targetType, builder ); + this.targetWriteAccessorType = targetWriteAccessor.getAccessorType(); return (T) this; } - public T targetWriteAccessor(ExecutableElement targetWriteAccessor) { - this.targetWriteAccessor = targetWriteAccessor; + T mirror(AnnotationMirror mirror) { + this.positionHint = mirror; return (T) this; } - public T targetPropertyName(String targetPropertyName) { - this.targetPropertyName = targetPropertyName; + public T sourcePropertyName(String sourcePropertyName) { + this.sourcePropertyName = sourcePropertyName; return (T) this; } - public T dependsOn(List dependsOn) { + public T dependsOn(Set dependsOn) { this.dependsOn = dependsOn; return (T) this; } @@ -116,337 +139,692 @@ public T existingVariableNames(Set existingVariableNames) { this.existingVariableNames = existingVariableNames; return (T) this; } + + protected boolean isFieldAssignment() { + return targetWriteAccessorType.isFieldAssignment(); + } } public static class PropertyMappingBuilder extends MappingBuilderBase { // initial properties - private String dateFormat; private String defaultValue; - private List qualifiers; - private TypeMirror resultType; + private String defaultJavaExpression; + private String conditionJavaExpression; private SourceReference sourceReference; + private SourceRHS rightHandSide; + private FormattingParameters formattingParameters; + private SelectionParameters selectionParameters; + private MappingControl mappingControl; + private MappingReferences forgeMethodWithMappingReferences; + private boolean forceUpdateMethod; + private boolean forgedNamedBased = true; + private NullValueCheckStrategyGem nvcs; + private NullValuePropertyMappingStrategyGem nvpms; + + PropertyMappingBuilder() { + super( PropertyMappingBuilder.class ); + } public PropertyMappingBuilder sourceReference(SourceReference sourceReference) { this.sourceReference = sourceReference; return this; } - public PropertyMappingBuilder qualifiers(List qualifiers) { - this.qualifiers = qualifiers; + public PropertyMappingBuilder selectionParameters(SelectionParameters selectionParameters) { + this.selectionParameters = selectionParameters; return this; } - public PropertyMappingBuilder resultType(TypeMirror resultType) { - this.resultType = resultType; + public PropertyMappingBuilder formattingParameters(FormattingParameters formattingParameters) { + this.formattingParameters = formattingParameters; return this; } - public PropertyMappingBuilder dateFormat(String dateFormat) { - this.dateFormat = dateFormat; + public PropertyMappingBuilder defaultValue(String defaultValue) { + this.defaultValue = defaultValue; return this; } - public PropertyMappingBuilder defaultValue(String defaultValue) { - this.defaultValue = defaultValue; + public PropertyMappingBuilder defaultJavaExpression(String defaultJavaExpression) { + this.defaultJavaExpression = defaultJavaExpression; return this; } - private enum TargetWriteAccessorType { - GETTER, - SETTER, - ADDER + public PropertyMappingBuilder conditionJavaExpression(String conditionJavaExpression) { + this.conditionJavaExpression = conditionJavaExpression; + return this; } - public PropertyMapping build() { + public PropertyMappingBuilder forgeMethodWithMappingReferences(MappingReferences mappingReferences) { + this.forgeMethodWithMappingReferences = mappingReferences; + return this; + } - // handle target - TargetWriteAccessorType targetAccessorType = getTargetAcccessorType(); - Type targetType = getTargetType( targetAccessorType ); + /** + * Force the created mapping to use update methods when forging a method. + * + * @param forceUpdateMethod whether the mapping should force update method for forged mappings + * @return the builder for chaining + */ + public PropertyMappingBuilder forceUpdateMethod(boolean forceUpdateMethod) { + this.forceUpdateMethod = forceUpdateMethod; + return this; + } - // handle source - String sourceElement = getSourceElement(); - Type sourceType = getSourceType(); - String sourceRefStr; - if ( targetAccessorType == TargetWriteAccessorType.ADDER && sourceType.isCollectionType() ) { - // handle adder, if source is collection then use iterator element type as source type. - // sourceRef becomes a local variable in the itereation. - sourceType = sourceType.getTypeParameters().get( 0 ); - sourceRefStr = Executables.getElementNameForAdder( targetWriteAccessor ); - } - else { - sourceRefStr = getSourceRef(); + /** + * @param forgedNamedBased mapping is based on forging + * + * @return the builder for chaining + */ + public PropertyMappingBuilder forgedNamedBased(boolean forgedNamedBased) { + this.forgedNamedBased = forgedNamedBased; + return this; + } + + public PropertyMappingBuilder options(DelegatingOptions options) { + this.mappingControl = options.getMappingControl( ctx.getElementUtils() ); + this.nvcs = options.getNullValueCheckStrategy(); + if ( method.isUpdateMethod() ) { + this.nvpms = options.getNullValuePropertyMappingStrategy(); } + return this; + } + + public PropertyMapping build() { + + // handle source + this.rightHandSide = getSourceRHS( sourceReference ); + + ctx.getMessager().note( 2, Message.PROPERTYMAPPING_MAPPING_NOTE, rightHandSide, targetWriteAccessor ); + + rightHandSide.setUseElementAsSourceTypeForMatching( + targetWriteAccessorType == AccessorType.ADDER ); // all the tricky cases will be excluded for the time being. boolean preferUpdateMethods; - if ( targetAccessorType == TargetWriteAccessorType.ADDER ) { + if ( targetWriteAccessorType == AccessorType.ADDER ) { preferUpdateMethods = false; } else { preferUpdateMethods = method.getMappingTargetParameter() != null; } - Assignment assignment = ctx.getMappingResolver().getTargetAssignment( - method, - sourceElement, - sourceType, - targetType, + SelectionCriteria criteria = SelectionCriteria.forMappingMethods( + selectionParameters, + mappingControl, targetPropertyName, - dateFormat, - qualifiers, - resultType, - sourceRefStr, preferUpdateMethods ); - // No mapping found. Try to forge a mapping - if ( assignment == null ) { - assignment = forgeMapOrIterableMapping( sourceType, targetType, sourceRefStr, method.getExecutable() ); + // forge a method instead of resolving one when there are mapping options. + Assignment assignment = null; + if ( forgeMethodWithMappingReferences == null ) { + assignment = ctx.getMappingResolver().getTargetAssignment( + method, + getForgedMethodHistory( rightHandSide ), + targetType, + formattingParameters, + criteria, + rightHandSide, + positionHint, + this::forge + ); + } + else { + assignment = forge(); + } + + // JSpecify: raise a hard compile error when a source that is not guaranteed @NonNull + // (either explicit @Nullable or unknown nullability outside a @NullMarked scope) is + // mapped to a @NonNull constructor parameter without a default value. Neither a null + // check (which would leave the variable null, violating the @NonNull contract) nor + // passing the value through is safe here — defaultValue / defaultExpression are the + // supported remedies. + // Skip when assignment resolution already failed: the user sees the primary + // "can't find mapping" error and a duplicate would only obscure the root cause. + if ( assignment != null + && targetWriteAccessorType == AccessorType.PARAMETER + && !hasDefaultValueOrDefaultExpression() ) { + NullabilityResolver.Nullability sourceNullability = getSourceJSpecifyNullability(); + NullabilityResolver.Nullability targetNullability = ctx.getNullabilityResolver().getSetterNullability( + targetWriteAccessor.getElement(), this::targetDeclaringTypeIsNullMarked + ); + if ( sourceNullability != NullabilityResolver.Nullability.NON_NULL + && targetNullability == NullabilityResolver.Nullability.NON_NULL ) { + ctx.getMessager().printMessage( + method.getExecutable(), + positionHint, + Message.PROPERTYMAPPING_NULLABLE_SOURCE_TO_NON_NULL_CONSTRUCTOR_PARAM, + sourcePropertyName != null ? sourcePropertyName : targetPropertyName, + targetPropertyName + ); + } } + Type sourceType = rightHandSide.getSourceType(); if ( assignment != null ) { + ctx.getMessager().note( 2, Message.PROPERTYMAPPING_SELECT_NOTE, assignment ); if ( targetType.isCollectionOrMapType() ) { - assignment = assignCollection( targetType, targetAccessorType, assignment ); + assignment = assignToCollection( targetType, targetWriteAccessorType, assignment ); } else if ( targetType.isArrayType() && sourceType.isArrayType() && assignment.getType() == DIRECT ) { - Type arrayType = ctx.getTypeFactory().getType( Arrays.class ); - assignment = new ArrayCopyWrapper( - assignment, - targetPropertyName, - arrayType, - targetType, - existingVariableNames - ); - assignment = new NullCheckWrapper( assignment ); + assignment = assignToArray( targetType, assignment ); } else { - assignment = assignObject( sourceType, targetType, targetAccessorType, assignment ); + assignment = assignToPlain( targetType, targetWriteAccessorType, assignment ); } - } else { - ctx.getMessager().printMessage( - method.getExecutable(), - Message.PROPERTYMAPPING_MAPPING_NOT_FOUND, - sourceElement, - targetType, - targetPropertyName, - targetType, - getSourceType() /* original source type */ - ); + reportCannotCreateMapping(); } return new PropertyMapping( + sourcePropertyName, targetPropertyName, - sourceReference.getParameter().getName(), - targetWriteAccessor.getSimpleName().toString(), - targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, + sourceReference.getParameter().getOriginalName(), + targetWriteAccessor.getSimpleName(), + targetReadAccessor, targetType, assignment, dependsOn, - getDefaultValueAssignment() + getDefaultValueAssignment( assignment ), + targetWriteAccessorType == AccessorType.PARAMETER ); } - private Assignment getDefaultValueAssignment() { - if ( defaultValue != null && !getSourceType().isPrimitive() ) { + private Assignment forge( ) { + Assignment assignment; + Type sourceType = rightHandSide.getSourceType(); + if ( ( sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType() + || ( sourceType.isIterableType() && targetType.isCollectionType() ) ) { + assignment = forgeIterableMapping( sourceType, targetType, rightHandSide ); + } + else if ( sourceType.isMapType() && targetType.isMapType() ) { + assignment = forgeMapMapping( sourceType, targetType, rightHandSide ); + } + else if ( sourceType.isMapType() && !targetType.isMapType() ) { + assignment = forgeMapping( sourceType, targetType.withoutBounds(), rightHandSide ); + } + else if ( ( sourceType.isIterableType() && targetType.isStreamType() ) + || ( sourceType.isStreamType() && targetType.isStreamType() ) + || ( sourceType.isStreamType() && targetType.isIterableType() ) ) { + assignment = forgeStreamMapping( sourceType, targetType, rightHandSide ); + } + else { + assignment = forgeMapping( rightHandSide ); + } + if ( assignment != null ) { + ctx.getMessager().note( 2, Message.PROPERTYMAPPING_CREATE_NOTE, assignment ); + } + return assignment; + } + + /** + * Report that a mapping could not be created. + */ + private void reportCannotCreateMapping() { + if ( forgeMethodWithMappingReferences != null && ctx.isErroneous() ) { + // If we arrived here, there is an error it means that we couldn't forge a mapping method + // so skip the cannot create mapping + return; + } + if ( method instanceof ForgedMethod && ( (ForgedMethod) method ).getHistory() != null ) { + // The history that is part of the ForgedMethod misses the information from the current right hand + // side. Therefore we need to extract the most relevant history and use that in the error reporting. + ForgedMethodHistory history = getForgedMethodHistory( rightHandSide ); + reportCannotCreateMapping( + method, + positionHint, + history.createSourcePropertyErrorMessage(), + history.getSourceType(), + history.getTargetType(), + history.createTargetPropertyName() + ); + } + else { + reportCannotCreateMapping( + method, + positionHint, + rightHandSide.getSourceErrorMessagePart(), + rightHandSide.getSourceType(), + targetType, + targetPropertyName + ); + } + } + + private Assignment getDefaultValueAssignment( Assignment rhs ) { + if ( defaultValue != null + && ( !rhs.getSourceType().isPrimitive() || rhs.getSourcePresenceCheckerReference() != null) ) { + // cannot check on null source if source is primitive unless it has a presence checker PropertyMapping build = new ConstantMappingBuilder() - .constantExpression( '"' + defaultValue + '"' ) - .dateFormat( dateFormat ) - .qualifiers( qualifiers ) - .resultType( resultType ) - .dependsOn( dependsOn ) - .existingVariableNames( existingVariableNames ) - .mappingContext( ctx ) - .sourceMethod( method ) - .targetPropertyName( targetPropertyName ) - .targetReadAccessor( targetReadAccessor ) - .targetWriteAccessor( targetWriteAccessor ) - .build(); + .constantExpression( defaultValue ) + .formattingParameters( formattingParameters ) + .selectionParameters( selectionParameters ) + .dependsOn( dependsOn ) + .existingVariableNames( existingVariableNames ) + .mappingContext( ctx ) + .sourceMethod( method ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) + .build(); + return build.getAssignment(); + } + if ( defaultJavaExpression != null + && ( !rhs.getSourceType().isPrimitive() || rhs.getSourcePresenceCheckerReference() != null) ) { + // cannot check on null source if source is primitive unless it has a presence checker + PropertyMapping build = new JavaExpressionMappingBuilder() + .javaExpression( defaultJavaExpression ) + .dependsOn( dependsOn ) + .existingVariableNames( existingVariableNames ) + .mappingContext( ctx ) + .sourceMethod( method ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) + .build(); return build.getAssignment(); } return null; } - private Assignment assignObject(Type sourceType, Type targetType, TargetWriteAccessorType targetAccessorType, - Assignment rhs) { + private Assignment assignToPlain(Type targetType, AccessorType targetAccessorType, + Assignment rightHandSide) { - Assignment result = rhs; + Assignment result; - if ( targetAccessorType == TargetWriteAccessorType.SETTER ) { - if ( result.isUpdateMethod() ) { - if ( targetReadAccessor == null ) { - ctx.getMessager().printMessage( method.getExecutable(), - Message.PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE, - targetPropertyName ); - } - Assignment factoryMethod = - ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null ); - result = new UpdateWrapper( result, method.getThrownTypes(), factoryMethod, - targetType ); - } - else { - result = new SetterWrapper( result, method.getThrownTypes() ); + if ( targetAccessorType == AccessorType.SETTER || targetAccessorType.isFieldAssignment() ) { + result = assignToPlainViaSetter( targetType, rightHandSide ); + } + else { + result = assignToPlainViaAdder( rightHandSide ); + } + return result; + + } + + private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) { + + if ( rhs.isCallingUpdateMethod() ) { + if ( targetReadAccessor == null ) { + ctx.getMessager().printMessage( + method.getExecutable(), + positionHint, + Message.PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE, + targetPropertyName + ); } - if ( !sourceType.isPrimitive() - && !sourceReference.getPropertyEntries().isEmpty() ) { // parameter null taken care of by beanmapper - if ( result.isUpdateMethod() ) { - result = new UpdateNullCheckWrapper( result ); + Assignment factory = ObjectFactoryMethodResolver + .getFactoryMethod( method, targetType, SelectionParameters.forSourceRHS( rightHandSide ), ctx ); + + if ( factory == null && targetBuilderType != null ) { + // If there is no dedicated factory method and the target has a builder we will try to use that + MethodReference builderFactoryMethod = ObjectFactoryMethodResolver.getBuilderFactoryMethod( + targetType, + targetBuilderType + ); + + if ( builderFactoryMethod != null ) { + + MethodReference finisherMethod = BuilderFinisherMethodResolver.getBuilderFinisherMethod( + method, + targetBuilderType, + ctx + ); + + if ( finisherMethod != null ) { + factory = MethodReference.forMethodChaining( builderFactoryMethod, finisherMethod ); + } } - else if ( result.getType() == TYPE_CONVERTED - || result.getType() == TYPE_CONVERTED_MAPPED - || result.getType() == MAPPED_TYPE_CONVERTED - || ( result.getType() == DIRECT && targetType.isPrimitive() ) ) { - // for primitive types null check is not possible at all, but a conversion needs - // a null check. - result = new NullCheckWrapper( result ); + + } + boolean includeSourceNullCheck = !rhs.isSourceReferenceParameter(); + if ( includeSourceNullCheck ) { + // JSpecify: source @NonNull means no null check needed + NullabilityResolver.Nullability sourceNullability = getSourceJSpecifyNullability(); + if ( sourceNullability == NullabilityResolver.Nullability.NON_NULL ) { + includeSourceNullCheck = false; } } + reportErrorWhenSetToDefaultCannotConstructTarget( targetType, factory ); + return new UpdateWrapper( + rhs, + method.getThrownTypes(), + factory, + isFieldAssignment(), + targetType, + includeSourceNullCheck, + nvpms == SET_TO_NULL && !targetType.isPrimitive(), + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName() + ); } else { - // TargetAccessorType must be ADDER - if ( getSourceType().isCollectionType() ) { - result = new AdderWrapper( - result, - method.getThrownTypes(), - getSourceRef(), - sourceType - ); - result = new NullCheckWrapper( result ); - } - else { - // Possibly adding null to a target collection. So should be surrounded by an null check. - result = new SetterWrapper( result, method.getThrownTypes() ); - result = new NullCheckWrapper( result ); + // If the property mapping has a default value assignment then we have to do a null value check + boolean includeSourceNullCheck = setterWrapperNeedsSourceNullCheck( rhs, targetType ); + if ( !includeSourceNullCheck ) { + // solution for #834 introduced a local var and null check for nested properties always. + // however, a local var is not needed if there's no need to check for null. + rhs.setSourceLocalVarName( null ); } + reportErrorWhenSetToDefaultCannotConstructTarget( targetType, null ); + return new SetterWrapper( + rhs, + method.getThrownTypes(), + isFieldAssignment(), + includeSourceNullCheck, + includeSourceNullCheck && nvpms == SET_TO_NULL && !targetType.isPrimitive(), + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType + ); } - return result; + } + /** + * For {@code NullValuePropertyMappingStrategy.SET_TO_DEFAULT} the target property is reset to a new + * default instance when the source is {@code null}. When that default instance is constructed via a + * parameterless constructor ({@code new Target()}) and the target type does not provide one, MapStruct + * would generate uncompilable code such as {@code target.setLocalDate( new LocalDate() )}. Report a + * compilation error in that case instead. + * + * @param targetType the type of the target property + * @param factory the factory used to construct the default instance, or {@code null} if none is used + */ + private void reportErrorWhenSetToDefaultCannotConstructTarget(Type targetType, Assignment factory) { + if ( nvpms != SET_TO_DEFAULT || factory != null ) { + // the default instance is either not created or created through a factory / builder method + return; + } + + Type typeToConstruct = targetType.isOptionalType() ? targetType.getOptionalBaseType() : targetType; + if ( typeToConstruct.getImplementationType() != null + || typeToConstruct.isArrayType() + || typeToConstruct.isOptionalType() + || typeToConstruct.getSensibleDefault() != null ) { + // these are initialized without invoking a parameterless constructor on the target type + return; + } + + if ( !typeToConstruct.hasAccessibleParameterlessConstructor() ) { + ctx.getMessager().printMessage( + method.getExecutable(), + positionHint, + Message.PROPERTYMAPPING_NO_ACCESSIBLE_PARAMETERLESS_CONSTRUCTOR, + typeToConstruct.describe() + ); + } } - private Assignment assignCollection(Type targetType, - TargetWriteAccessorType targetAccessorType, - Assignment rhs) { + /** + * Checks whether the setter wrapper should include a null / presence check or not + * + * @param rhs the source right hand side + * @param targetType the target type + * + * @return whether to include a null / presence check or not + */ + private boolean setterWrapperNeedsSourceNullCheck(Assignment rhs, Type targetType) { + if ( rhs.getSourceType().isPrimitive() && rhs.getSourcePresenceCheckerReference() == null ) { + // If the source type is primitive or it doesn't have a presence checker then + // we shouldn't do a null check + return false; + } + + // JSpecify: source @NonNull means the value is guaranteed non-null, skip all checks + NullabilityResolver.Nullability sourceNullability = getSourceJSpecifyNullability(); + if ( sourceNullability == NullabilityResolver.Nullability.NON_NULL ) { + ctx.getMessager().note( 2, + Message.PROPERTYMAPPING_JSPECIFY_SKIP_NULL_CHECK_NON_NULL_SOURCE, + targetPropertyName + ); + return false; + } + + if ( rhs.getSourcePresenceCheckerReference() != null ) { + // There is an explicit source presence check method -> do a null / presence check + return true; + } + + if ( nvpms == SET_TO_DEFAULT || nvpms == IGNORE ) { + // NullValuePropertyMapping is SET_TO_DEFAULT or IGNORE -> do a null check + return true; + } + + if ( rhs.getType().isConverted() ) { + // A type conversion is applied, so a null check is required + return true; + } + + if ( rhs.getType().isDirect() && targetType.isPrimitive() ) { + // If the type is direct and the target type is primitive (i.e. we are unboxing) then check is needed + return true; + } + + if ( hasDefaultValueOrDefaultExpression() ) { + // If there is default value defined then a check is needed + return true; + } + + // JSpecify annotations take precedence over NullValueCheckStrategy + NullabilityResolver resolver = ctx.getNullabilityResolver(); + NullabilityResolver.Nullability targetNullability = resolver.getSetterNullability( + targetWriteAccessor.getElement(), this::targetDeclaringTypeIsNullMarked + ); + Boolean jspecifyDecision = resolver.requiresNullCheck( sourceNullability, targetNullability ); + if ( jspecifyDecision != null ) { + ctx.getMessager().note( 2, + jspecifyDecision + ? Message.PROPERTYMAPPING_JSPECIFY_ADD_NULL_CHECK + : Message.PROPERTYMAPPING_JSPECIFY_SKIP_NULL_CHECK, + targetPropertyName, + sourceNullability, + targetNullability + ); + return jspecifyDecision; + } - Assignment result = rhs; + if ( nvcs == ALWAYS ) { + // NullValueCheckStrategy is ALWAYS -> do a null check + return true; + } - // wrap the setter in the collection / map initializers - if ( targetAccessorType == TargetWriteAccessorType.SETTER ) { + return false; + } - // wrap the assignment in a new Map or Collection implementation if this is not done in a - // mapping method. Note, typeconversons do not apply to collections or maps - Assignment newCollectionOrMap = null; - if ( result.getType() == DIRECT ) { - Set implementationTypes; - if ( targetType.getImplementationType() != null ) { - implementationTypes = targetType.getImplementationType().getImportTypes(); + private NullabilityResolver.Nullability getSourceJSpecifyNullability() { + if ( sourceReference == null ) { + return NullabilityResolver.Nullability.UNKNOWN; + } + List entries = sourceReference.getPropertyEntries(); + if ( !entries.isEmpty() ) { + // A source chain can only be treated as @NonNull when every accessor along the + // chain is @NonNull. If any intermediate accessor is @Nullable, the chain may + // yield null even when the deepest accessor is @NonNull. + Type enclosingType = sourceReference.getParameter().getType(); + NullabilityResolver.Nullability chain = NullabilityResolver.Nullability.NON_NULL; + for ( PropertyEntry entry : entries ) { + if ( entry.getReadAccessor() == null ) { + return NullabilityResolver.Nullability.UNKNOWN; } - else { - implementationTypes = targetType.getImportTypes(); + NullabilityResolver.Nullability current = ctx.getNullabilityResolver().getNullability( + entry.getReadAccessor().getElement(), enclosingType::isNullMarked + ); + if ( current == NullabilityResolver.Nullability.NULLABLE ) { + return NullabilityResolver.Nullability.NULLABLE; } - newCollectionOrMap = new NewCollectionOrMapWrapper( result, implementationTypes ); - newCollectionOrMap = new SetterWrapper( newCollectionOrMap, method.getThrownTypes() ); - } - if ( result.isUpdateMethod() ) { - if ( targetReadAccessor == null ) { - ctx.getMessager().printMessage( method.getExecutable(), - Message.PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE, - targetPropertyName ); + if ( current == NullabilityResolver.Nullability.UNKNOWN ) { + chain = NullabilityResolver.Nullability.UNKNOWN; } - Assignment factoryMethod - = ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null ); - result = new UpdateWrapper( result, method.getThrownTypes(), factoryMethod, - targetType ); - } - else { - // wrap the assignment in the setter method - result = new SetterWrapper( result, method.getThrownTypes() ); - - // target accessor is setter, so wrap the setter in setter map/ collection handling - result = new SetterWrapperForCollectionsAndMaps( - result, - targetWriteAccessor, - newCollectionOrMap - ); + enclosingType = entry.getType(); } - + return chain; } - else { - // target accessor is getter, so wrap the setter in getter map/ collection handling - result = new GetterWrapperForCollectionsAndMaps( - result, - method.getThrownTypes(), - ctx.getTypeFactory().asCollectionOrMap( targetType ), - existingVariableNames - ); + // Direct parameter mapping: no property entries, the source is the parameter itself. + // Use the mapper type for @NullMarked scope resolution since the parameter is declared there. + Parameter parameter = sourceReference.getParameter(); + if ( parameter != null && parameter.getElement() != null ) { + return ctx.getNullabilityInMapperScope( parameter.getElement() ); } + return NullabilityResolver.Nullability.UNKNOWN; + } - // For collections and maps include a null check, when the assignment type is DIRECT. - // for mapping methods (builtin / custom), the mapping method is responsible for the - // null check. Typeconversions do not apply to collections and maps. - if ( result.getType() == DIRECT ) { - result = new NullCheckWrapper( result ); + /** + * Resolves whether the type that declares the target write accessor (i.e. the bean that + * owns the setter or field) is in a JSpecify {@code @NullMarked} scope. This is the correct + * scope for deciding whether an unannotated setter parameter or field should be treated as + * {@code @NonNull} — walking from the property value type (e.g. {@code String}) does not + * reach the bean's declaration. + */ + private boolean targetDeclaringTypeIsNullMarked() { + Element targetElement = targetWriteAccessor.getElement(); + if ( targetElement == null ) { + return false; } - else if ( result.getType() == MAPPED && result.isUpdateMethod() ) { - result = new UpdateNullCheckWrapper( result ); + Element declaring = targetElement.getEnclosingElement(); + if ( !( declaring instanceof TypeElement ) ) { + return false; } + return ctx.getTypeFactory().getType( declaring.asType() ).isNullMarked(); + } - return result; + private boolean hasDefaultValueOrDefaultExpression() { + return defaultValue != null || defaultJavaExpression != null; } - private Type getSourceType() { + private Assignment assignToPlainViaAdder( Assignment rightHandSide) { - Parameter sourceParam = sourceReference.getParameter(); - List propertyEntries = sourceReference.getPropertyEntries(); - if ( propertyEntries.isEmpty() ) { - return sourceParam.getType(); + Assignment result = rightHandSide; + + String adderIteratorName = sourcePropertyName == null ? targetPropertyName : sourcePropertyName; + if ( result.getSourceType().isIterableType() ) { + result = new AdderWrapper( result, method.getThrownTypes(), isFieldAssignment(), adderIteratorName ); } - else if ( propertyEntries.size() == 1 ) { - PropertyEntry propertyEntry = propertyEntries.get( 0 ); - return propertyEntry.getType(); + else if ( result.getSourceType().isStreamType() ) { + result = new StreamAdderWrapper( + result, method.getThrownTypes(), isFieldAssignment(), adderIteratorName ); } else { - PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 ); - return lastPropertyEntry.getType(); + // Possibly adding null to a target collection. So should be surrounded by an null check. + // TODO: what triggers this else branch? Should nvcs, nvpms be applied? + result = new SetterWrapper( result, + method.getThrownTypes(), + isFieldAssignment(), + true, + nvpms == SET_TO_NULL && !targetType.isPrimitive(), + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType + ); + } + return result; + } + + private boolean hasTwoOrMoreSettersWithName() { + Element enclosingClass = this.targetWriteAccessor.getElement().getEnclosingElement(); + if ( enclosingClass == null || !( ElementKind.CLASS.equals( enclosingClass.getKind() ) + || ElementKind.INTERFACE.equals( enclosingClass.getKind() ) ) ) { + return false; + } + String simpleWriteAccessorName = this.targetWriteAccessor.getSimpleName(); + boolean firstMatchFound = false; + for ( Accessor setter : ctx.getTypeFactory().getType( enclosingClass.asType() ).getSetters() ) { + if ( setter.getSimpleName().equals( simpleWriteAccessorName ) ) { + if ( firstMatchFound ) { + return true; + } + firstMatchFound = true; + } } + return false; } - private String getSourceRef() { + private Assignment assignToCollection(Type targetType, AccessorType targetAccessorType, + Assignment rhs) { + return new CollectionAssignmentBuilder() + .mappingBuilderContext( ctx ) + .method( method ) + .targetReadAccessor( targetReadAccessor ) + .targetType( targetType ) + .targetPropertyName( targetPropertyName ) + .targetAccessorType( targetAccessorType ) + .rightHandSide( rightHandSide ) + .assignment( rhs ) + .nullValueCheckStrategy( hasDefaultValueOrDefaultExpression() ? ALWAYS : nvcs ) + .nullValuePropertyMappingStrategy( nvpms ) + .sourceJSpecifyNullability( getSourceJSpecifyNullability() ) + .build(); + } + + private Assignment assignToArray(Type targetType, Assignment rightHandSide) { + + Type arrayType = ctx.getTypeFactory().getType( Arrays.class ); + //TODO init default value + return new ArrayCopyWrapper( + rightHandSide, + targetPropertyName, + arrayType, + targetType, + isFieldAssignment(), + nvpms == SET_TO_NULL && !targetType.isPrimitive(), + nvpms == SET_TO_DEFAULT ); + } + + private SourceRHS getSourceRHS( SourceReference sourceReference ) { Parameter sourceParam = sourceReference.getParameter(); - List propertyEntries = sourceReference.getPropertyEntries(); + PropertyEntry propertyEntry = sourceReference.getDeepestProperty(); // parameter reference - if ( propertyEntries.isEmpty() ) { - return sourceParam.getName(); + if ( propertyEntry == null ) { + SourceRHS sourceRHS = new SourceRHS( + sourceParam.getName(), + sourceParam.getType(), + existingVariableNames, + sourceReference.toString() + ); + sourceRHS.setSourcePresenceCheckerReference( getSourcePresenceCheckerRef( + sourceReference, + sourceRHS + ) ); + return sourceRHS; } // simple property - else if ( propertyEntries.size() == 1 ) { - PropertyEntry propertyEntry = propertyEntries.get( 0 ); - return sourceParam.getName() + "." + propertyEntry.getAccessor().getSimpleName() + "()"; + else if ( !sourceReference.isNested() ) { + String sourceRef = sourceParam.getName() + "." + propertyEntry.getReadAccessor().getReadValueSource(); + SourceRHS sourceRHS = new SourceRHS( + sourceParam.getName(), + sourceRef, + null, + propertyEntry.getType(), + existingVariableNames, + sourceReference.toString() + ); + sourceRHS.setSourcePresenceCheckerReference( getSourcePresenceCheckerRef( + sourceReference, + sourceRHS + ) ); + return sourceRHS; } // nested property given as dot path else { - PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 ); - - // copy mapper configuration from the source method, its the same mapper - MapperConfiguration config = method.getMapperConfiguration(); + Type sourceType = propertyEntry.getType(); + if ( sourceType.isPrimitive() && !targetType.isPrimitive() ) { + // Handle null's. If the forged method needs to be mapped to an object, the forged method must be + // able to return null. So in that case primitive types are mapped to their corresponding wrapped + // type. The source type becomes the wrapped type in that case. + sourceType = ctx.getTypeFactory().getWrappedType( sourceType ); + } // forge a method from the parameter type to the last entry type. String forgedName = Strings.joinAndCamelize( sourceReference.getElementNames() ); - forgedName = Strings.getSaveVariableName( forgedName, ctx.getNamesOfMappingsToGenerate() ); - ForgedMethod methodRef = new ForgedMethod( - forgedName, - sourceReference.getParameter().getType(), - lastPropertyEntry.getType(), - config, - method.getExecutable() - ); + forgedName = Strings.getSafeVariableName( forgedName, ctx.getReservedNames() ); + Type sourceParameterType = sourceReference.getParameter().getType(); + ForgedMethod methodRef = forParameterMapping( forgedName, sourceParameterType, sourceType, method ); + NestedPropertyMappingMethod.Builder builder = new NestedPropertyMappingMethod.Builder(); NestedPropertyMappingMethod nestedPropertyMapping = builder .method( methodRef ) .propertyEntries( sourceReference.getPropertyEntries() ) + .mappingContext( ctx ) .build(); // add if not yet existing @@ -456,118 +834,215 @@ else if ( propertyEntries.size() == 1 ) { else { forgedName = ctx.getExistingMappingMethod( nestedPropertyMapping ).getName(); } + String sourceRef = forgedName + "( " + sourceParam.getName() + " )"; + SourceRHS sourceRhs = new SourceRHS( sourceParam.getName(), + sourceRef, + null, + sourceType, + existingVariableNames, + sourceReference.toString() + ); + sourceRhs.setSourcePresenceCheckerReference( getSourcePresenceCheckerRef( + sourceReference, + sourceRhs + ) ); - return forgedName + "( " + sourceParam.getName() + " )"; - } - } + // create a local variable to which forged method can be assigned. + String desiredName = propertyEntry.getName(); + sourceRhs.setSourceLocalVarName( sourceRhs.createUniqueVarName( desiredName ) ); - private String getSourceElement() { + return sourceRhs; - Parameter sourceParam = sourceReference.getParameter(); - List propertyEntries = sourceReference.getPropertyEntries(); - if ( propertyEntries.isEmpty() ) { - return String.format( "parameter \"%s %s\"", sourceParam.getType(), sourceParam.getName() ); - } - else if ( propertyEntries.size() == 1 ) { - PropertyEntry propertyEntry = propertyEntries.get( 0 ); - return String.format( "property \"%s %s\"", propertyEntry.getType(), propertyEntry.getName() ); - } - else { - PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 ); - return String.format( - "property \"%s %s\"", - lastPropertyEntry.getType(), - Strings.join( sourceReference.getElementNames(), "." ) - ); } } - private TargetWriteAccessorType getTargetAcccessorType() { - if ( Executables.isSetterMethod( targetWriteAccessor ) ) { - return TargetWriteAccessorType.SETTER; + private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReference, + SourceRHS sourceRHS) { + + if ( conditionJavaExpression != null ) { + return new JavaExpressionPresenceCheck( conditionJavaExpression ); } - else if ( Executables.isAdderMethod( targetWriteAccessor ) ) { - return TargetWriteAccessorType.ADDER; + + SelectionParameters selectionParameters = this.selectionParameters != null ? + this.selectionParameters.withSourceRHS( sourceRHS ) : + SelectionParameters.forSourceRHS( sourceRHS ); + PresenceCheck presenceCheck = PresenceCheckMethodResolver.getPresenceCheck( + method, + selectionParameters, + ctx + ); + if ( presenceCheck != null ) { + return presenceCheck; } - else { - return TargetWriteAccessorType.GETTER; + + PresenceCheck sourcePresenceChecker = null; + if ( !sourceReference.getPropertyEntries().isEmpty() ) { + Parameter sourceParam = sourceReference.getParameter(); + // TODO is first correct here?? shouldn't it be last since the remainer is checked + // in the forged method? + PropertyEntry propertyEntry = sourceReference.getShallowestProperty(); + if ( propertyEntry.getPresenceChecker() != null ) { + List presenceChecks = new ArrayList<>(); + presenceChecks.add( new SuffixPresenceCheck( + sourceParam.getName(), + propertyEntry.getPresenceChecker().getPresenceCheckSuffix() + ) ); + + String variableName = sourceParam.getName() + "." + + propertyEntry.getReadAccessor().getReadValueSource(); + Type variableType = propertyEntry.getType(); + for ( int i = 1; i < sourceReference.getPropertyEntries().size(); i++ ) { + PropertyEntry entry = sourceReference.getPropertyEntries().get( i ); + if ( entry.getPresenceChecker() != null && entry.getReadAccessor() != null ) { + if ( variableType.isOptionalType() ) { + presenceChecks.add( new OptionalPresenceCheck( + variableName, + ctx.getVersionInformation() + ) ); + variableName = variableName + ".get()"; + } + else { + presenceChecks.add( new NullPresenceCheck( variableName ) ); + } + presenceChecks.add( new SuffixPresenceCheck( + variableName, + entry.getPresenceChecker().getPresenceCheckSuffix() + ) ); + variableName = variableName + "." + entry.getReadAccessor().getSimpleName() + "()"; + variableType = entry.getType(); + } + else { + break; + } + } + + if ( presenceChecks.size() == 1 ) { + sourcePresenceChecker = presenceChecks.get( 0 ); + } + else { + sourcePresenceChecker = new AllPresenceChecksPresenceCheck( presenceChecks ); + } + } } + return sourcePresenceChecker; } - private Type getTargetType(TargetWriteAccessorType targetAccessorType) { - switch ( targetAccessorType ) { - case ADDER: - case SETTER: - return ctx.getTypeFactory().getSingleParameter( - method.getResultType().getTypeElement(), - targetWriteAccessor ).getType(); - case GETTER: - default: - return ctx.getTypeFactory().getReturnType( - method.getResultType().getTypeElement(), - targetWriteAccessor ); - } + private Assignment forgeStreamMapping(Type sourceType, Type targetType, SourceRHS source) { + + StreamMappingMethod.Builder builder = new StreamMappingMethod.Builder(); + return forgeWithElementMapping( sourceType, targetType, source, builder ); } - private Assignment forgeMapOrIterableMapping(Type sourceType, Type targetType, String sourceReference, - ExecutableElement element) { + private Assignment forgeIterableMapping(Type sourceType, Type targetType, SourceRHS source) { - Assignment assignment = null; + IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder(); + return forgeWithElementMapping( sourceType, targetType, source, builder ); + } + private Assignment forgeWithElementMapping(Type sourceType, Type targetType, SourceRHS source, + ContainerMappingMethodBuilder builder) { + sourceType = sourceType.replaceSuperBoundWith( targetType, ctx.getTypeFactory().getType( Object.class ) ); + targetType = targetType.withoutBounds(); + ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "[]" ); + + Supplier mappingMethodCreator = () -> builder + .mappingContext( ctx ) + .method( methodRef ) + .selectionParameters( selectionParameters ) + .callingContextTargetPropertyName( targetPropertyName ) + .positionHint( positionHint ) + .build(); + + return getOrCreateForgedAssignment( source, methodRef, mappingMethodCreator ); + } + + private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source, String suffix) { String name = getName( sourceType, targetType ); - name = Strings.getSaveVariableName( name, ctx.getNamesOfMappingsToGenerate() ); + name = Strings.getSafeVariableName( name, ctx.getReservedNames() ); - if ( ( sourceType.isCollectionType() || sourceType.isArrayType() ) - && ( targetType.isCollectionType() || targetType.isArrayType() ) ) { + // copy mapper configuration from the source method, its the same mapper + ForgedMethodHistory forgedMethodHistory = getForgedMethodHistory( source, suffix ); + return forElementMapping( name, sourceType, targetType, method, forgedMethodHistory, forgedNamedBased ); + } - // copy mapper configuration from the source method, its the same mapper - MapperConfiguration config = method.getMapperConfiguration(); - ForgedMethod methodRef = new ForgedMethod( name, sourceType, targetType, config, element ); - IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder(); + private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS source) { - IterableMappingMethod iterableMappingMethod = builder - .mappingContext( ctx ) - .method( methodRef ) - .build(); + targetType = targetType.withoutBounds(); + ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "{}" ); - if ( iterableMappingMethod != null ) { - if ( !ctx.getMappingsToGenerate().contains( iterableMappingMethod ) ) { - ctx.getMappingsToGenerate().add( iterableMappingMethod ); - } - else { - String existingName = ctx.getExistingMappingMethod( iterableMappingMethod ).getName(); - methodRef = new ForgedMethod( existingName, methodRef ); - } + MapMappingMethod.Builder builder = new MapMappingMethod.Builder(); + Supplier mapMappingMethodCreator = () -> builder + .mappingContext( ctx ) + .method( methodRef ) + .build(); - assignment = AssignmentFactory.createMethodReference( methodRef, null, targetType ); - assignment.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - } + return getOrCreateForgedAssignment( source, methodRef, mapMappingMethodCreator ); + } + + private Assignment forgeMapping(SourceRHS sourceRHS) { + Type sourceType; + if ( targetWriteAccessorType == AccessorType.ADDER ) { + sourceType = sourceRHS.getSourceTypeForMatching(); + } + else { + sourceType = sourceRHS.getSourceType(); + } + if ( forgedNamedBased && !canGenerateAutoSubMappingBetween( sourceType, targetType ) ) { + return null; } - else if ( sourceType.isMapType() && targetType.isMapType() ) { - // copy mapper configuration from the source method, its the same mapper - MapperConfiguration config = method.getMapperConfiguration(); - ForgedMethod methodRef = new ForgedMethod( name, sourceType, targetType, config, element ); + return forgeMapping( sourceType, targetType, sourceRHS ); + } - MapMappingMethod.Builder builder = new MapMappingMethod.Builder(); - MapMappingMethod mapMappingMethod = builder - .mappingContext( ctx ) - .method( methodRef ) - .build(); + private Assignment forgeMapping(Type sourceType, Type targetType, SourceRHS sourceRHS) { - if ( mapMappingMethod != null ) { - if ( !ctx.getMappingsToGenerate().contains( mapMappingMethod ) ) { - ctx.getMappingsToGenerate().add( mapMappingMethod ); - } - else { - String existingName = ctx.getExistingMappingMethod( mapMappingMethod ).getName(); - methodRef = new ForgedMethod( existingName, methodRef ); - } - assignment = AssignmentFactory.createMethodReference( methodRef, null, targetType ); - assignment.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - } + //Fail fast. If we could not find the method by now, no need to try + if ( sourceType.isPrimitive() || targetType.isPrimitive() ) { + return null; } - return assignment; + + String name = getName( sourceType, targetType ); + name = Strings.getSafeVariableName( name, ctx.getReservedNames() ); + + List parameters = new ArrayList<>( method.getContextParameters() ); + Type returnType; + // there's only one case for forging a method with mapping options: nested target properties. + // They should forge an update method only if we set the forceUpdateMethod. This is set to true, + // because we are forging a Mapping for a method with multiple source parameters. + // If the target type is enum, then we can't create an update method + if ( !targetType.isEnumType() && ( method.isUpdateMethod() || forceUpdateMethod ) + && targetWriteAccessorType != AccessorType.ADDER ) { + parameters.add( Parameter.forForgedMappingTarget( targetType ) ); + returnType = ctx.getTypeFactory().createVoidType(); + } + else { + returnType = targetType; + } + ForgedMethod forgedMethod = forPropertyMapping( name, + sourceType, + returnType, + parameters, + method, + getForgedMethodHistory( sourceRHS ), + forgeMethodWithMappingReferences, + forgedNamedBased + ); + return createForgedAssignment( sourceRHS, forgedMethod ); + } + + private ForgedMethodHistory getForgedMethodHistory(SourceRHS sourceRHS) { + return getForgedMethodHistory( sourceRHS, "" ); + } + + private ForgedMethodHistory getForgedMethodHistory(SourceRHS sourceRHS, String suffix) { + ForgedMethodHistory history = null; + if ( method instanceof ForgedMethod ) { + ForgedMethod method = (ForgedMethod) this.method; + history = method.getHistory(); + } + return new ForgedMethodHistory( history, getSourceElementName() + suffix, + targetPropertyName + suffix, sourceRHS.getSourceType(), targetType, true, "property" + ); } private String getName(Type sourceType, Type targetType) { @@ -584,186 +1059,269 @@ private String getName(Type type) { builder.append( type.getIdentification() ); return builder.toString(); } + + private String getSourceElementName() { + Parameter sourceParam = sourceReference.getParameter(); + List propertyEntries = sourceReference.getPropertyEntries(); + if ( propertyEntries.isEmpty() ) { + return sourceParam.getName(); + } + else if ( propertyEntries.size() == 1 ) { + PropertyEntry propertyEntry = propertyEntries.get( 0 ); + return propertyEntry.getName(); + } + else { + return Strings.join( sourceReference.getElementNames(), "." ); + } + } } public static class ConstantMappingBuilder extends MappingBuilderBase { private String constantExpression; - private String dateFormat; - private List qualifiers; - private TypeMirror resultType; + private FormattingParameters formattingParameters; + private MappingControl mappingControl; + private SelectionParameters selectionParameters; + + ConstantMappingBuilder() { + super( ConstantMappingBuilder.class ); + } public ConstantMappingBuilder constantExpression(String constantExpression) { this.constantExpression = constantExpression; return this; } - public ConstantMappingBuilder dateFormat(String dateFormat) { - this.dateFormat = dateFormat; + public ConstantMappingBuilder formattingParameters(FormattingParameters formattingParameters) { + this.formattingParameters = formattingParameters; return this; } - public ConstantMappingBuilder qualifiers(List qualifiers) { - this.qualifiers = qualifiers; + public ConstantMappingBuilder selectionParameters(SelectionParameters selectionParameters) { + this.selectionParameters = selectionParameters; return this; } - public ConstantMappingBuilder resultType(TypeMirror resultType) { - this.resultType = resultType; + public ConstantMappingBuilder options(MappingOptions options) { + this.mappingControl = options.getMappingControl( ctx.getElementUtils() ); return this; } public PropertyMapping build() { - // source - String mappedElement = "constant '" + constantExpression + "'"; - Type sourceType = ctx.getTypeFactory().getType( String.class ); + String sourceErrorMessagePart = "constant '" + constantExpression + "'"; + String errorMessageDetails = null; - // target - Type targetType; - if ( Executables.isSetterMethod( targetWriteAccessor ) ) { - targetType = ctx.getTypeFactory().getSingleParameter( - method.getResultType().getTypeElement(), - targetWriteAccessor ).getType(); + Class baseForLiteral = null; + try { + baseForLiteral = NativeTypes.getLiteral( targetType.getFullyQualifiedName(), constantExpression ); } - else { - targetType = ctx.getTypeFactory().getReturnType( - method.getResultType().getTypeElement(), - targetWriteAccessor ); + catch ( IllegalArgumentException ex ) { + errorMessageDetails = ex.getMessage(); } - Assignment assignment = ctx.getMappingResolver().getTargetAssignment( - method, - mappedElement, - sourceType, - targetType, + // the constant is not a primitive literal, assume it to be a String + if ( baseForLiteral == null ) { + constantExpression = "\"" + constantExpression + "\""; + baseForLiteral = String.class; + } + Type sourceType = ctx.getTypeFactory().getTypeForLiteral( baseForLiteral ); + + SelectionCriteria criteria = SelectionCriteria.forMappingMethods( selectionParameters, + mappingControl, targetPropertyName, - dateFormat, - qualifiers, - resultType, - constantExpression, - method.getMappingTargetParameter() != null + method.getMappingTargetParameter() != null ); + Assignment assignment = null; + if ( !targetType.isEnumType() ) { + assignment = ctx.getMappingResolver().getTargetAssignment( + method, + null, // TODO description for constant + targetType, + formattingParameters, + criteria, + new SourceRHS( constantExpression, sourceType, existingVariableNames, sourceErrorMessagePart ), + positionHint, + () -> null + ); + } + else { + assignment = getEnumAssignment(); + } + if ( assignment != null ) { - if ( Executables.isSetterMethod( targetWriteAccessor ) ) { + if ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER || + targetWriteAccessor.getAccessorType().isFieldAssignment() ) { // target accessor is setter, so decorate assignment as setter - if ( assignment.isUpdateMethod() ) { + if ( assignment.isCallingUpdateMethod() ) { if ( targetReadAccessor == null ) { - ctx.getMessager().printMessage( method.getExecutable(), + ctx.getMessager().printMessage( + method.getExecutable(), + positionHint, Message.CONSTANTMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE, - targetPropertyName ); + targetPropertyName + ); } + Assignment factoryMethod = - ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null ); - assignment = new UpdateWrapper( assignment, method.getThrownTypes(), factoryMethod, - targetType ); + ObjectFactoryMethodResolver.getFactoryMethod( method, targetType, null, ctx ); + + assignment = new UpdateWrapper( + assignment, + method.getThrownTypes(), + factoryMethod, + isFieldAssignment(), + targetType, + false, + false, + false, + false ); } else { - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); + assignment = new SetterWrapper( assignment, method.getThrownTypes(), isFieldAssignment() ); } } else { // target accessor is getter, so getter map/ collection handling - assignment = new GetterWrapperForCollectionsAndMaps( - assignment, - method.getThrownTypes(), - ctx.getTypeFactory().asCollectionOrMap( targetType ), - existingVariableNames - ); + assignment = new GetterWrapperForCollectionsAndMaps( assignment, + method.getThrownTypes(), + targetType, + isFieldAssignment() + ); } } - else { + else if ( errorMessageDetails == null ) { ctx.getMessager().printMessage( method.getExecutable(), + positionHint, Message.CONSTANTMAPPING_MAPPING_NOT_FOUND, - sourceType, constantExpression, - targetType, + targetType.describe(), targetPropertyName ); } + else { + ctx.getMessager().printMessage( + method.getExecutable(), + positionHint, + Message.CONSTANTMAPPING_MAPPING_NOT_FOUND_WITH_DETAILS, + constantExpression, + targetType.describe(), + targetPropertyName, + errorMessageDetails + ); + } return new PropertyMapping( targetPropertyName, - targetWriteAccessor.getSimpleName().toString(), - targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, + targetWriteAccessor.getSimpleName(), + targetReadAccessor, targetType, assignment, dependsOn, - null + null, + targetWriteAccessorType == AccessorType.PARAMETER ); } + + private Assignment getEnumAssignment() { + Assignment assignment = null; + // String String quotation marks. + String enumExpression = constantExpression.substring( 1, constantExpression.length() - 1 ); + if ( targetType.getEnumConstants().contains( enumExpression ) ) { + String sourceErrorMessagePart = "constant '" + constantExpression + "'"; + assignment = new SourceRHS( enumExpression, targetType, existingVariableNames, sourceErrorMessagePart ); + assignment = new EnumConstantWrapper( assignment, targetType ); + } + else { + ctx.getMessager().printMessage( + method.getExecutable(), + positionHint, + Message.CONSTANTMAPPING_NON_EXISTING_CONSTANT, + constantExpression, + targetType.describe(), + targetPropertyName + ); + } + return assignment; + } + } public static class JavaExpressionMappingBuilder extends MappingBuilderBase { private String javaExpression; + JavaExpressionMappingBuilder() { + super( JavaExpressionMappingBuilder.class ); + } + public JavaExpressionMappingBuilder javaExpression(String javaExpression) { this.javaExpression = javaExpression; return this; } public PropertyMapping build() { + Assignment assignment = new SourceRHS( javaExpression, null, existingVariableNames, "" ); - Assignment assignment = AssignmentFactory.createDirect( javaExpression ); - - Type targetType; - if ( Executables.isSetterMethod( targetWriteAccessor ) ) { + if ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER || + targetWriteAccessor.getAccessorType().isFieldAssignment() ) { // setter, so wrap in setter - assignment = new SetterWrapper( assignment, method.getThrownTypes() ); - targetType = ctx.getTypeFactory().getSingleParameter( - method.getResultType().getTypeElement(), - targetWriteAccessor ).getType(); + assignment = new SetterWrapper( assignment, method.getThrownTypes(), isFieldAssignment() ); } else { - targetType = ctx.getTypeFactory().getReturnType( - method.getResultType().getTypeElement(), - targetWriteAccessor ); // target accessor is getter, so wrap the setter in getter map/ collection handling - assignment = new GetterWrapperForCollectionsAndMaps( - assignment, - method.getThrownTypes(), - ctx.getTypeFactory().asCollectionOrMap( targetType ), - existingVariableNames - ); + assignment = new GetterWrapperForCollectionsAndMaps( assignment, + method.getThrownTypes(), + targetType, + isFieldAssignment() + ); } return new PropertyMapping( targetPropertyName, - targetWriteAccessor.getSimpleName().toString(), - targetReadAccessor != null ? targetReadAccessor.getSimpleName().toString() : null, + targetWriteAccessor.getSimpleName(), + targetReadAccessor, targetType, assignment, dependsOn, - null + null, + targetWriteAccessorType == AccessorType.PARAMETER ); } } // Constructor for creating mappings of constant expressions. - private PropertyMapping(String name, String targetWriteAccessorName, String targetReadAccessorName, Type targetType, - Assignment propertyAssignment, List dependsOn, Assignment defaultValueAssignment ) { - this( name, null, targetWriteAccessorName, targetReadAccessorName, - targetType, propertyAssignment, dependsOn, defaultValueAssignment ); + private PropertyMapping(String name, String targetWriteAccessorName, + ReadAccessor targetReadAccessorProvider, + Type targetType, Assignment propertyAssignment, + Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + this( name, null, null, targetWriteAccessorName, targetReadAccessorProvider, + targetType, propertyAssignment, dependsOn, defaultValueAssignment, + constructorMapping + ); } - private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName, - String targetReadAccessorName, Type targetType, Assignment assignment, - List dependsOn, Assignment defaultValueAssignment ) { + private PropertyMapping(String sourcePropertyName, String name, String sourceBeanName, + String targetWriteAccessorName, ReadAccessor targetReadAccessorProvider, Type targetType, + Assignment assignment, + Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + this.sourcePropertyName = sourcePropertyName; this.name = name; this.sourceBeanName = sourceBeanName; this.targetWriteAccessorName = targetWriteAccessorName; - this.targetReadAccessorName = targetReadAccessorName; + this.targetReadAccessorProvider = targetReadAccessorProvider; this.targetType = targetType; + this.assignment = assignment; - this.dependsOn = dependsOn != null ? dependsOn : Collections.emptyList(); + this.dependsOn = dependsOn != null ? dependsOn : Collections.emptySet(); this.defaultValueAssignment = defaultValueAssignment; + this.constructorMapping = constructorMapping; } /** @@ -773,6 +1331,10 @@ public String getName() { return name; } + public String getSourcePropertyName() { + return sourcePropertyName; + } + public String getSourceBeanName() { return sourceBeanName; } @@ -782,7 +1344,7 @@ public String getTargetWriteAccessorName() { } public String getTargetReadAccessorName() { - return targetReadAccessorName; + return targetReadAccessorProvider == null ? null : targetReadAccessorProvider.getReadValueSource(); } public Type getTargetType() { @@ -797,21 +1359,62 @@ public Assignment getDefaultValueAssignment() { return defaultValueAssignment; } + public boolean isConstructorMapping() { + return constructorMapping; + } + @Override public Set getImportTypes() { - return assignment.getImportTypes(); + if ( defaultValueAssignment == null ) { + return assignment.getImportTypes(); + } + + return org.mapstruct.ap.internal.util.Collections.asSet( + assignment.getImportTypes(), + defaultValueAssignment.getImportTypes() + ); } - public List getDependsOn() { + public Set getDependsOn() { return dependsOn; } + @Override + public int hashCode() { + int hash = 5; + hash = 67 * hash + (this.name != null ? this.name.hashCode() : 0); + hash = 67 * hash + (this.targetType != null ? this.targetType.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + final PropertyMapping other = (PropertyMapping) obj; + if ( !Objects.equals( name, other.name ) ) { + return false; + } + if ( !Objects.equals( targetType, other.targetType ) ) { + return false; + } + + return true; + } + @Override public String toString() { return "PropertyMapping {" + "\n name='" + name + "\'," + "\n targetWriteAccessorName='" + targetWriteAccessorName + "\'," - + "\n targetReadAccessorName='" + targetReadAccessorName + "\'," + + "\n targetReadAccessorName='" + getTargetReadAccessorName() + "\'," + "\n targetType=" + targetType + "," + "\n propertyAssignment=" + assignment + "," + "\n defaultValueAssignment=" + defaultValueAssignment + "," diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java index a22f053e94..0d97c1a523 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ServicesEntry.java @@ -1,30 +1,19 @@ -/** - * Copyright 2012-2015 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.model; -import org.mapstruct.ap.internal.model.common.ModelElement; -import org.mapstruct.ap.internal.model.common.Type; - import java.util.Collections; import java.util.Set; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + /** + * Represents a service entry for the service loader file. + * * @author Christophe Labouisse on 14/07/2015. */ public class ServicesEntry extends ModelElement { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java new file mode 100644 index 0000000000..3955bc47ce --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java @@ -0,0 +1,138 @@ +/* + * 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.model; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one iterable or array type to Stream. + * The collection elements are mapped either by a {@link TypeConversion} or another mapping method. + * + * @author Filip Hrisafov + */ +public class StreamMappingMethod extends ContainerMappingMethod { + + private final Set helperImports; + + public static class Builder extends ContainerMappingMethodBuilder { + + public Builder() { + super( Builder.class, "stream element" ); + } + + @Override + protected Type getElementType(Type parameterType) { + return StreamMappingMethod.getElementType( parameterType ); + } + + @Override + protected Assignment getWrapper(Assignment assignment, Method method) { + return new Java8FunctionWrapper( assignment ); + } + + @Override + protected StreamMappingMethod instantiateMappingMethod(Method method, Collection existingVariables, + Assignment assignment, MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, + List beforeMappingMethods, + List afterMappingMethods, SelectionParameters selectionParameters, + PresenceCheck sourceParameterPresenceCheck) { + + Set helperImports = new HashSet<>(); + if ( method.getResultType().isIterableType() ) { + helperImports.add( ctx.getTypeFactory().getType( Collectors.class ) ); + } + + Type sourceParameterType = first( method.getSourceParameters() ).getType(); + if ( !sourceParameterType.isCollectionType() && !sourceParameterType.isArrayType() && + sourceParameterType.isIterableType() ) { + helperImports.add( ctx.getTypeFactory().getType( StreamSupport.class ) ); + } + return new StreamMappingMethod( + method, + getMethodAnnotations(), + existingVariables, + assignment, + factoryMethod, + mapNullToDefault, + loopVariableName, + beforeMappingMethods, + afterMappingMethods, + selectionParameters, + helperImports, + sourceParameterPresenceCheck + ); + } + } + //CHECKSTYLE:OFF + private StreamMappingMethod(Method method, List annotations, + Collection existingVariables, Assignment parameterAssignment, + MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, + List beforeMappingReferences, + List afterMappingReferences, + SelectionParameters selectionParameters, Set helperImports, PresenceCheck sourceParameterPresenceCheck) { + super( + method, + annotations, + existingVariables, + parameterAssignment, + factoryMethod, + mapNullToDefault, + loopVariableName, + beforeMappingReferences, + afterMappingReferences, + selectionParameters, + sourceParameterPresenceCheck + ); + //CHECKSTYLE:ON + this.helperImports = helperImports; + } + + @Override + public Set getImportTypes() { + Set types = super.getImportTypes(); + + types.addAll( helperImports ); + + return types; + } + + public Type getSourceElementType() { + return getElementType( getSourceParameter().getType() ); + } + + public Type getResultElementType() { + return getElementType( getResultType() ); + } + + private static Type getElementType(Type parameterType) { + if ( parameterType.isArrayType() ) { + return parameterType.getComponentType(); + } + else if ( parameterType.isIterableType() ) { + return first( parameterType.determineTypeArguments( Iterable.class ) ).getTypeBound(); + } + else if ( parameterType.isStreamType() ) { + return first( parameterType.determineTypeArguments( Stream.class ) ).getTypeBound(); + } + + throw new IllegalArgumentException( "Could not get the element type" ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/SubclassMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/SubclassMapping.java new file mode 100644 index 0000000000..3ce42f68e5 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SubclassMapping.java @@ -0,0 +1,69 @@ +/* + * 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.model; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.assignment.AssignmentWrapper; +import org.mapstruct.ap.internal.model.assignment.ReturnWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Represents the mapping between a Subclass and its mapping target. This will be used by a {@link BeanMappingMethod} + * that has {@link org.mapstruct.SubclassMapping} annotations applied to it. Before doing the normal mapping for that + * method it will first check if the source object is of the sourceType if so it will use the assignment instead. + * + * @author Ben Zegveld + */ +public class SubclassMapping extends ModelElement { + + private final Type sourceType; + private final Type targetType; + private final Assignment assignment; + private final String sourceArgument; + + public SubclassMapping(Type sourceType, String sourceArgument, Type targetType, Assignment assignment) { + this.sourceType = sourceType; + this.sourceArgument = sourceArgument; + this.targetType = targetType; + this.assignment = assignment; + } + + public Type getSourceType() { + return sourceType; + } + + @Override + public Set getImportTypes() { + return Collections.singleton( sourceType ); + } + + public AssignmentWrapper getAssignment() { + return new ReturnWrapper( assignment ); + } + + public String getSourceArgument() { + return sourceArgument; + } + + @Override + public boolean equals(final Object other) { + if ( !( other instanceof SubclassMapping ) ) { + return false; + } + SubclassMapping castOther = (SubclassMapping) other; + return Objects.equals( sourceType, castOther.sourceType ) && Objects.equals( targetType, castOther.targetType ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceType, targetType ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingConstructorFragment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingConstructorFragment.java new file mode 100644 index 0000000000..936a9a51ba --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingConstructorFragment.java @@ -0,0 +1,105 @@ +/* + * 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.model; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConstructorFragment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * A mapper instance field, initialized as null + * + * @author Sjaak Derksen + */ +public class SupportingConstructorFragment extends ModelElement { + + private final String variableName; + private final String templateName; + private final SupportingMappingMethod definingMethod; + + public SupportingConstructorFragment(SupportingMappingMethod definingMethod, + ConstructorFragment constructorFragment, String variableName) { + this.templateName = getTemplateNameForClass( constructorFragment.getClass() ); + this.definingMethod = definingMethod; + this.variableName = variableName; + } + + @Override + public String getTemplateName() { + return templateName; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + public SupportingMappingMethod getDefiningMethod() { + return definingMethod; + } + + public String getVariableName() { + return variableName; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( variableName == null ) ? 0 : variableName.hashCode() ); + result = prime * result + ( ( templateName == null ) ? 0 : templateName.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + SupportingConstructorFragment other = (SupportingConstructorFragment) obj; + + if ( !Objects.equals( variableName, other.variableName ) ) { + return false; + } + if ( !Objects.equals( templateName, other.templateName ) ) { + return false; + } + return true; + } + + public static void addAllFragmentsIn(Set supportingMappingMethods, + Set targets) { + for ( SupportingMappingMethod supportingMappingMethod : supportingMappingMethods ) { + SupportingConstructorFragment fragment = supportingMappingMethod.getSupportingConstructorFragment(); + if ( fragment != null ) { + targets.add( supportingMappingMethod.getSupportingConstructorFragment() ); + } + } + } + + public static SupportingConstructorFragment getSafeConstructorFragment(SupportingMappingMethod method, + ConstructorFragment fragment, + Field supportingField) { + if ( fragment == null ) { + return null; + } + + return new SupportingConstructorFragment( + method, + fragment, + supportingField != null ? supportingField.getVariableName() : null ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java new file mode 100644 index 0000000000..2cac1ef351 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.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.ap.internal.model; + +import java.util.Map; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.FieldReference; +import org.mapstruct.ap.internal.util.Strings; + +/** + * supports the + * + * @author Sjaak Derksen + */ +public class SupportingField extends Field { + + private final String templateName; + private final Map templateParameter; + + private final SupportingMappingMethod definingMethod; + + public SupportingField(SupportingMappingMethod definingMethod, FieldReference fieldReference, String name) { + super( fieldReference.getType(), name, true ); + this.templateName = getTemplateNameForClass( fieldReference.getClass() ); + this.templateParameter = fieldReference.getTemplateParameter(); + this.definingMethod = definingMethod; + } + + @Override + public String getTemplateName() { + return templateName; + } + + public Map getTemplateParameter() { + return templateParameter; + } + + public SupportingMappingMethod getDefiningMethod() { + return definingMethod; + } + + public static void addAllFieldsIn(Set supportingMappingMethods, Set targets) { + for ( SupportingMappingMethod supportingMappingMethod : supportingMappingMethods ) { + Field field = supportingMappingMethod.getSupportingField(); + if ( field != null ) { + targets.add( field ); + } + } + } + + public static Field getSafeField(SupportingMappingMethod method, FieldReference ref, Set existingFields) { + if ( ref == null ) { + return null; + } + + String name = ref.getVariableName(); + for ( Field existingField : existingFields ) { + if ( existingField.getVariableName().equals( ref.getVariableName() ) + && existingField.getType().equals( ref.getType() ) ) { + // field already exists, use that one + return existingField; + } + } + name = Strings.getSafeVariableName( name, Field.getFieldNames( existingFields ) ); + return new SupportingField( method, ref, name ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingMappingMethod.java new file mode 100644 index 0000000000..9428454c1f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingMappingMethod.java @@ -0,0 +1,129 @@ +/* + * 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.model; + +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; +import org.mapstruct.ap.internal.model.source.builtin.NewDatatypeFactoryConstructorFragment; + +/** + * A mapping method which is not based on an actual method declared in the original mapper interface but is added as + * private method to map a certain source/target type combination. Based on a {@link BuiltInMethod}. + * + * Specific templates all point to this class, for instance: + * {@link org.mapstruct.ap.internal.model.source.builtin.XmlGregorianCalendarToCalendar}, + * but also used fields and constructor elements, e.g. + * {@link org.mapstruct.ap.internal.model.common.FinalField} and + * {@link NewDatatypeFactoryConstructorFragment} + * + * @author Gunnar Morling + */ +public class SupportingMappingMethod extends MappingMethod { + + private final String templateName; + private final Set importTypes; + private final Field supportingField; + private final SupportingConstructorFragment supportingConstructorFragment; + private final Map templateParameter; + + public SupportingMappingMethod(BuiltInMethod method, Set existingFields) { + super( method ); + this.importTypes = method.getImportTypes(); + this.templateName = getTemplateNameForClass( method.getClass() ); + this.templateParameter = null; + this.supportingField = SupportingField.getSafeField( this, method.getFieldReference(), existingFields ); + this.supportingConstructorFragment = SupportingConstructorFragment.getSafeConstructorFragment( + this, + method.getConstructorFragment(), + this.supportingField ); + } + + public SupportingMappingMethod(HelperMethod method) { + super( method ); + this.importTypes = method.getImportTypes(); + this.templateName = getTemplateNameForClass( method.getClass() ); + this.templateParameter = null; + this.supportingField = null; + this.supportingConstructorFragment = null; + } + + @Override + public String getTemplateName() { + return templateName; + } + + @Override + public Set getImportTypes() { + return importTypes; + } + + /** + * Finds a {@link Type} by a given name. The {@code name} will be compared to the fully-qualified and also simple + * names of the {@code importTypes}. + * + * @param name Fully-qualified or simple name of the type. + * + * @return Found type, never null. + * + * @throws IllegalArgumentException In case no {@link Type} was found for given name. + */ + public Type findType(String name) { + for ( Type type : importTypes ) { + if ( type.getFullyQualifiedName().contentEquals( name ) ) { + return type; + } + if ( type.getName().contentEquals( name ) ) { + return type; + } + } + + throw new IllegalArgumentException( "No type for given name '" + name + "' found in 'importTypes'." ); + } + + public Field getSupportingField() { + return supportingField; + } + + public SupportingConstructorFragment getSupportingConstructorFragment() { + return supportingConstructorFragment; + } + + public Map getTemplateParameter() { + return templateParameter; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( getName() == null ) ? 0 : getName().hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + SupportingMappingMethod other = (SupportingMappingMethod) obj; + + if ( !Objects.equals( getName(), other.getName() ) ) { + return false; + } + + return true; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.java new file mode 100644 index 0000000000..45bbc9d4b0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.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.ap.internal.model; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * An inline conversion from a source to an optional of the source. + * + * @author Filip Hrisafov + */ +public class ToOptionalTypeConversion extends ModelElement implements Assignment { + + private final Assignment conversionAssignment; + private final Type targetType; + + public ToOptionalTypeConversion(Type targetType, Assignment conversionAssignment) { + this.conversionAssignment = conversionAssignment; + this.targetType = targetType; + } + + public Type getTargetType() { + return targetType; + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>( conversionAssignment.getImportTypes() ); + importTypes.add( targetType ); + return importTypes; + } + + @Override + public List getThrownTypes() { + return conversionAssignment.getThrownTypes(); + } + + public Assignment getAssignment() { + return conversionAssignment; + } + + @Override + public String getSourceReference() { + return conversionAssignment.getSourceReference(); + } + + @Override + public boolean isSourceReferenceParameter() { + return conversionAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return conversionAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return conversionAssignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName) { + return conversionAssignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return conversionAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + conversionAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return conversionAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + conversionAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return conversionAssignment.getSourceParameterName(); + } + + @Override + public void setAssignment(Assignment assignment) { + conversionAssignment.setAssignment( assignment ); + } + + @Override + public AssignmentType getType() { + return conversionAssignment.getType(); + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return targetType.getName() + ".of( " + conversionAssignment.toString() + " )"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/TypeConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/model/TypeConversion.java index f90241e746..c5b4bc6915 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/TypeConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/TypeConversion.java @@ -1,31 +1,19 @@ -/** - * Copyright 2012-2015 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.model; -import org.mapstruct.ap.internal.model.assignment.Assignment; -import org.mapstruct.ap.internal.model.common.ModelElement; -import org.mapstruct.ap.internal.model.common.Type; - import java.util.HashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + /** * An inline conversion between source and target type of a mapping. * @@ -47,10 +35,10 @@ public class TypeConversion extends ModelElement implements Assignment { */ private Assignment assignment; - TypeConversion( Set importTypes, - List exceptionTypes, - String expression ) { - this.importTypes = new HashSet( importTypes ); + public TypeConversion( Set importTypes, + List exceptionTypes, + String expression ) { + this.importTypes = new HashSet<>( importTypes ); this.importTypes.addAll( exceptionTypes ); this.thrownTypes = exceptionTypes; @@ -86,6 +74,51 @@ public String getSourceReference() { return assignment.getSourceReference(); } + @Override + public boolean isSourceReferenceParameter() { + return assignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return assignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return assignment.getSourceType(); + } + + @Override + public String createUniqueVarName(String desiredName ) { + return assignment.createUniqueVarName( desiredName ); + } + + @Override + public String getSourceLocalVarName() { + return assignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + assignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return assignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + assignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return assignment.getSourceParameterName(); + } + @Override public void setAssignment( Assignment assignment ) { this.assignment = assignment; @@ -105,8 +138,13 @@ public Assignment.AssignmentType getType() { } @Override - public boolean isUpdateMethod() { - return false; + public boolean isCallingUpdateMethod() { + return false; } + @Override + public String toString() { + String argument = getAssignment() != null ? getAssignment().toString() : getSourceReference(); + return openExpression + argument + closeExpression; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java new file mode 100644 index 0000000000..82346fa8ac --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java @@ -0,0 +1,676 @@ +/* + * 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.model; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.BeanMappingGem; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.EnumMappingOptions; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; +import org.mapstruct.ap.internal.model.source.ValueMappingOptions; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.internal.version.VersionInformation; +import org.mapstruct.ap.spi.EnumTransformationStrategy; + +import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_REMAINING; +import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_UNMAPPED; +import static org.mapstruct.ap.internal.gem.MappingConstantsGem.NULL; +import static org.mapstruct.ap.internal.gem.MappingConstantsGem.THROW_EXCEPTION; +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * A {@link ValueMappingMethod} which maps one value type to another, optionally configured by one or more + * {@link ValueMappingOptions}s. For now, only enum-to-enum mapping is supported. + * + * @author Sjaak Derksen + */ +public class ValueMappingMethod extends MappingMethod { + + private final List annotations; + private final List valueMappings; + private final MappingEntry defaultTarget; + private final MappingEntry nullTarget; + private final VersionInformation versionInformation; + + private final Type unexpectedValueMappingException; + + private final boolean overridden; + + public static class Builder { + + private Method method; + private MappingBuilderContext ctx; + private ValueMappings valueMappings; + private EnumMappingOptions enumMapping; + private EnumTransformationStrategyInvoker enumTransformationInvoker; + private boolean enumTransformationIllegalReported = false; + + public Builder mappingContext(MappingBuilderContext mappingContext) { + this.ctx = mappingContext; + return this; + } + + public Builder method(Method sourceMethod) { + this.method = sourceMethod; + return this; + } + + public Builder valueMappings(List valueMappings) { + this.valueMappings = new ValueMappings( valueMappings ); + return this; + } + + public Builder enumMapping(EnumMappingOptions enumMapping) { + this.enumMapping = enumMapping; + return this; + } + + public ValueMappingMethod build() { + + if ( !enumMapping.isValid() ) { + return null; + } + + initializeEnumTransformationStrategy(); + + // initialize all relevant parameters + List mappingEntries = new ArrayList<>(); + + Type sourceType = first( method.getSourceParameters() ).getType(); + Type targetType = method.getResultType(); + + if ( targetType.isEnumType() && valueMappings.nullTarget == null ) { + // If null target is not set it means that the user has not explicitly defined a mapping for null + valueMappings.nullValueTarget = ctx.getEnumMappingStrategy() + .getDefaultNullEnumConstant( targetType.getTypeElement() ); + } + + // enum-to-enum + if ( sourceType.isEnumType() && targetType.isEnumType() ) { + mappingEntries.addAll( enumToEnumMapping( method, sourceType, targetType ) ); + } + else if ( sourceType.isEnumType() && targetType.isString() ) { + mappingEntries.addAll( enumToStringMapping( method, sourceType ) ); + } + else if ( sourceType.isString() && targetType.isEnumType() ) { + mappingEntries.addAll( stringToEnumMapping( method, targetType ) ); + } + + // do before / after lifecycle mappings + SelectionParameters selectionParameters = getSelectionParameters( method, ctx.getTypeUtils() ); + Set existingVariables = new HashSet<>( method.getParameterNames() ); + List beforeMappingMethods = + LifecycleMethodResolver.beforeMappingMethods( method, selectionParameters, ctx, existingVariables ); + List afterMappingMethods = + LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariables ); + List annotations; + if ( method instanceof ForgedMethod ) { + annotations = Collections.emptyList(); + } + else { + annotations = new ArrayList<>(); + AdditionalAnnotationsBuilder additionalAnnotationsBuilder = + new AdditionalAnnotationsBuilder( + ctx.getElementUtils(), + ctx.getTypeFactory(), + ctx.getMessager() + ); + + annotations.addAll( additionalAnnotationsBuilder.getProcessedAnnotations( method.getExecutable() ) ); + } + // finally return a mapping + return new ValueMappingMethod( + method, + annotations, + mappingEntries, + valueMappings.nullValueTarget, + valueMappings.defaultTargetValue, + determineUnexpectedValueMappingException(), + beforeMappingMethods, + afterMappingMethods, + ctx.getVersionInformation() + ); + } + + private void initializeEnumTransformationStrategy() { + if ( !enumMapping.hasNameTransformationStrategy() ) { + enumTransformationInvoker = EnumTransformationStrategyInvoker.DEFAULT; + } + else { + Map enumTransformationStrategies = + ctx.getEnumTransformationStrategies(); + + String nameTransformationStrategy = enumMapping.getNameTransformationStrategy(); + if ( enumTransformationStrategies.containsKey( nameTransformationStrategy ) ) { + enumTransformationInvoker = new EnumTransformationStrategyInvoker( + enumTransformationStrategies.get( + nameTransformationStrategy ), enumMapping.getNameTransformationConfiguration() + ); + } + } + + } + + private String transform(String source) { + try { + return enumTransformationInvoker.transform( source ); + } + catch ( IllegalArgumentException ex ) { + if ( !enumTransformationIllegalReported ) { + enumTransformationIllegalReported = true; + ctx.getMessager().printMessage( + method.getExecutable(), + enumMapping.getMirror(), + Message.ENUMMAPPING_ILLEGAL_TRANSFORMATION, + enumTransformationInvoker.transformationStrategy.getStrategyName(), + ex.getMessage() + ); + } + return source; + } + } + + private List enumToEnumMapping(Method method, Type sourceType, Type targetType) { + + List mappings = new ArrayList<>(); + List unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() ); + boolean sourceErrorOccurred = reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); + boolean targetErrorOccurred = reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); + if ( sourceErrorOccurred || targetErrorOccurred ) { + return mappings; + } + + // Start to fill the mappings with the defined value mappings + for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) { + mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) ); + unmappedSourceConstants.remove( valueMapping.getSource() ); + } + + // add mappings based on name + if ( !valueMappings.hasMapAnyUnmapped ) { + + // We store the target constants in a map in order to support inherited inverse mapping + // When using a nameTransformationStrategy the transformation should be done on the target enum + // instead of the source enum + Map targetConstants = new LinkedHashMap<>(); + + boolean enumMappingInverse = enumMapping.isInverse(); + TypeElement targetTypeElement = method.getReturnType().getTypeElement(); + for ( String targetEnumConstant : method.getReturnType().getEnumConstants() ) { + String targetNameEnum = getEnumConstant( targetTypeElement, targetEnumConstant ); + if ( enumMappingInverse ) { + // If the mapping is inverse we have to change the target enum constant + targetConstants.put( + transform( targetNameEnum ), + targetEnumConstant + ); + } + else { + targetConstants.put( targetNameEnum, targetEnumConstant ); + } + } + + TypeElement sourceTypeElement = sourceType.getTypeElement(); + for ( String sourceConstant : new ArrayList<>( unmappedSourceConstants ) ) { + String sourceNameConstant = getEnumConstant( sourceTypeElement, sourceConstant ); + String targetConstant; + if ( !enumMappingInverse ) { + targetConstant = transform( sourceNameConstant ); + } + else { + targetConstant = sourceNameConstant; + } + + if ( targetConstants.containsKey( targetConstant ) ) { + mappings.add( new MappingEntry( sourceConstant, targetConstants.get( targetConstant ) ) ); + unmappedSourceConstants.remove( sourceConstant ); + } + else if ( NULL.equals( targetConstant ) ) { + mappings.add( new MappingEntry( sourceConstant, null ) ); + unmappedSourceConstants.remove( sourceConstant ); + } + } + + if ( valueMappings.defaultTarget == null && !unmappedSourceConstants.isEmpty() ) { + String sourceErrorMessage = "source"; + String targetErrorMessage = "target"; + if ( method instanceof ForgedMethod && ( (ForgedMethod) method ).getHistory() != null ) { + ForgedMethodHistory history = ( (ForgedMethod) method ).getHistory(); + sourceErrorMessage = history.createSourcePropertyErrorMessage(); + targetErrorMessage = + "\"" + history.getTargetType().describe() + " " + history.createTargetPropertyName() + "\""; + } + // all sources should now be matched, there's no default to fall back to, so if sources remain, + // we have an issue. + ctx.getMessager().printMessage( + method.getExecutable(), + Message.VALUEMAPPING_UNMAPPED_SOURCES, + sourceErrorMessage, + targetErrorMessage, + Strings.join( unmappedSourceConstants, ", " ) + ); + + } + } + return mappings; + } + + private List enumToStringMapping(Method method, Type sourceType) { + + List mappings = new ArrayList<>(); + List unmappedSourceConstants = new ArrayList<>( sourceType.getEnumConstants() ); + boolean sourceErrorOccurred = reportErrorIfMappedSourceEnumConstantsDontExist( method, sourceType ); + boolean anyRemainingUsedError = reportErrorIfSourceEnumConstantsContainsAnyRemaining( method ); + if ( sourceErrorOccurred || anyRemainingUsedError ) { + return mappings; + } + + // Start to fill the mappings with the defined valueMappings + for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) { + mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) ); + unmappedSourceConstants.remove( valueMapping.getSource() ); + } + + // add mappings based on name + if ( !valueMappings.hasMapAnyUnmapped ) { + + TypeElement sourceTypeElement = sourceType.getTypeElement(); + // all remaining constants are mapped + for ( String sourceConstant : unmappedSourceConstants ) { + String sourceNameConstant = getEnumConstant( sourceTypeElement, sourceConstant ); + String targetConstant = transform( sourceNameConstant ); + mappings.add( new MappingEntry( sourceConstant, targetConstant ) ); + } + } + return mappings; + } + + private List stringToEnumMapping(Method method, Type targetType) { + + List mappings = new ArrayList<>(); + List unmappedSourceConstants = new ArrayList<>( targetType.getEnumConstants() ); + boolean sourceErrorOccurred = reportErrorIfMappedTargetEnumConstantsDontExist( method, targetType ); + reportWarningIfAnyRemainingOrAnyUnMappedMissing( method ); + if ( sourceErrorOccurred ) { + return mappings; + } + Set mappedSources = new LinkedHashSet<>(); + + // Start to fill the mappings with the defined value mappings + for ( ValueMappingOptions valueMapping : valueMappings.regularValueMappings ) { + mappedSources.add( valueMapping.getSource() ); + mappings.add( new MappingEntry( valueMapping.getSource(), valueMapping.getTarget() ) ); + unmappedSourceConstants.remove( valueMapping.getSource() ); + } + + // add mappings based on name + if ( !valueMappings.hasMapAnyUnmapped ) { + mappedSources.add( NULL ); + TypeElement targetTypeElement = targetType.getTypeElement(); + // all remaining constants are mapped + for ( String sourceConstant : unmappedSourceConstants ) { + String sourceNameConstant = getEnumConstant( targetTypeElement, sourceConstant ); + String stringConstant = transform( sourceNameConstant ); + if ( !mappedSources.contains( stringConstant ) ) { + mappings.add( new MappingEntry( stringConstant, sourceConstant ) ); + } + } + } + return mappings; + } + + private String getEnumConstant(TypeElement typeElement, String enumConstant) { + return ctx.getEnumMappingStrategy().getEnumConstant( typeElement, enumConstant ); + } + + private SelectionParameters getSelectionParameters(Method method, TypeUtils typeUtils) { + BeanMappingGem beanMapping = BeanMappingGem.instanceOn( method.getExecutable() ); + if ( beanMapping != null ) { + List qualifiers = beanMapping.qualifiedBy().get(); + List qualifyingNames = beanMapping.qualifiedByName().get(); + TypeMirror resultType = beanMapping.resultType().get(); + return new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + } + return null; + } + + private boolean reportErrorIfMappedSourceEnumConstantsDontExist(Method method, Type sourceType) { + List sourceEnumConstants = sourceType.getEnumConstants(); + + boolean foundIncorrectMapping = false; + + for ( ValueMappingOptions mappedConstant : valueMappings.regularValueMappings ) { + + if ( !enumMapping.isInverse() && THROW_EXCEPTION.equals( mappedConstant.getSource() ) ) { + ctx.getMessager().printMessage( + method.getExecutable(), + mappedConstant.getMirror(), + mappedConstant.getSourceAnnotationValue(), + Message.VALUEMAPPING_THROW_EXCEPTION_SOURCE + ); + foundIncorrectMapping = true; + } + else if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) { + + ctx.getMessager().printMessage( + method.getExecutable(), + mappedConstant.getMirror(), + mappedConstant.getSourceAnnotationValue(), + Message.VALUEMAPPING_NON_EXISTING_CONSTANT, + mappedConstant.getSource(), + sourceType + ); + foundIncorrectMapping = true; + } + } + return foundIncorrectMapping; + } + + private boolean reportErrorIfSourceEnumConstantsContainsAnyRemaining(Method method) { + boolean foundIncorrectMapping = false; + + if ( valueMappings.hasMapAnyRemaining ) { + ctx.getMessager().printMessage( + method.getExecutable(), + valueMappings.defaultTarget.getMirror(), + valueMappings.defaultTarget.getSourceAnnotationValue(), + Message.VALUEMAPPING_ANY_REMAINING_FOR_NON_ENUM, + method.getResultType() + ); + foundIncorrectMapping = true; + } + return foundIncorrectMapping; + } + + private void reportWarningIfAnyRemainingOrAnyUnMappedMissing(Method method) { + + if ( !( valueMappings.hasMapAnyUnmapped || valueMappings.hasMapAnyRemaining ) ) { + ctx.getMessager().printMessage( + method.getExecutable(), + Message.VALUEMAPPING_ANY_REMAINING_OR_UNMAPPED_MISSING + ); + } + } + + private boolean reportErrorIfMappedTargetEnumConstantsDontExist(Method method, Type targetType) { + List targetEnumConstants = targetType.getEnumConstants(); + + boolean foundIncorrectMapping = false; + + for ( ValueMappingOptions mappedConstant : valueMappings.regularValueMappings ) { + if ( !NULL.equals( mappedConstant.getTarget() ) + && !THROW_EXCEPTION.equals( mappedConstant.getTarget() ) + && !targetEnumConstants.contains( mappedConstant.getTarget() ) ) { + ctx.getMessager().printMessage( + method.getExecutable(), + mappedConstant.getMirror(), + mappedConstant.getTargetAnnotationValue(), + Message.VALUEMAPPING_NON_EXISTING_CONSTANT, + mappedConstant.getTarget(), + method.getReturnType() + ); + foundIncorrectMapping = true; + } + } + + if ( valueMappings.defaultTarget != null + && !THROW_EXCEPTION.equals( valueMappings.defaultTarget.getTarget() ) + && !NULL.equals( valueMappings.defaultTarget.getTarget() ) + && !targetEnumConstants.contains( valueMappings.defaultTarget.getTarget() ) ) { + ctx.getMessager().printMessage( + method.getExecutable(), + valueMappings.defaultTarget.getMirror(), + valueMappings.defaultTarget.getTargetAnnotationValue(), + Message.VALUEMAPPING_NON_EXISTING_CONSTANT, + valueMappings.defaultTarget.getTarget(), + method.getReturnType() + ); + foundIncorrectMapping = true; + } + + if ( valueMappings.nullTarget != null && NULL.equals( valueMappings.nullTarget.getTarget() ) + && !targetEnumConstants.contains( valueMappings.nullTarget.getTarget() ) ) { + ctx.getMessager().printMessage( + method.getExecutable(), + valueMappings.nullTarget.getMirror(), + valueMappings.nullTarget.getTargetAnnotationValue(), + Message.VALUEMAPPING_NON_EXISTING_CONSTANT, + valueMappings.nullTarget.getTarget(), + method.getReturnType() + ); + foundIncorrectMapping = true; + } + else if ( valueMappings.nullTarget == null && valueMappings.nullValueTarget != null + && !valueMappings.nullValueTarget.equals( THROW_EXCEPTION ) + && !targetEnumConstants.contains( valueMappings.nullValueTarget ) ) { + // if there is no nullTarget, but nullValueTarget has a value it means that there was an SPI + // which returned an enum for the target enum + ctx.getMessager().printMessage( + method.getExecutable(), + Message.VALUEMAPPING_NON_EXISTING_CONSTANT_FROM_SPI, + valueMappings.nullValueTarget, + method.getReturnType(), + ctx.getEnumMappingStrategy() + ); + } + + return foundIncorrectMapping; + } + + private Type determineUnexpectedValueMappingException() { + TypeMirror unexpectedValueMappingException = enumMapping.getUnexpectedValueMappingException(); + if ( unexpectedValueMappingException != null ) { + return ctx.getTypeFactory().getType( unexpectedValueMappingException ); + } + + return ctx.getTypeFactory() + .getType( ctx.getEnumMappingStrategy().getUnexpectedValueMappingExceptionType() ); + } + } + + private static class EnumTransformationStrategyInvoker { + + private static final EnumTransformationStrategyInvoker DEFAULT = new EnumTransformationStrategyInvoker( + null, + null + ); + + private final EnumTransformationStrategy transformationStrategy; + private final String configuration; + + private EnumTransformationStrategyInvoker( + EnumTransformationStrategy transformationStrategy, String configuration) { + this.transformationStrategy = transformationStrategy; + this.configuration = configuration; + } + + private String transform(String source) { + if ( transformationStrategy == null ) { + return source; + } + + return transformationStrategy.transform( source, configuration ); + } + } + + private static class ValueMappings { + + List regularValueMappings = new ArrayList<>(); + ValueMappingOptions defaultTarget = null; + String defaultTargetValue = null; + ValueMappingOptions nullTarget = null; + String nullValueTarget = null; + boolean hasMapAnyUnmapped = false; + boolean hasMapAnyRemaining = false; + boolean hasDefaultValue = false; + boolean hasAtLeastOneExceptionValue = false; + + ValueMappings(List valueMappings) { + + for ( ValueMappingOptions valueMapping : valueMappings ) { + if ( ANY_REMAINING.equals( valueMapping.getSource() ) ) { + defaultTarget = valueMapping; + defaultTargetValue = defaultTarget.getTarget(); + hasDefaultValue = true; + hasMapAnyRemaining = true; + } + else if ( ANY_UNMAPPED.equals( valueMapping.getSource() ) ) { + defaultTarget = valueMapping; + defaultTargetValue = defaultTarget.getTarget(); + hasDefaultValue = true; + hasMapAnyUnmapped = true; + } + else if ( NULL.equals( valueMapping.getSource() ) ) { + nullTarget = valueMapping; + nullValueTarget = getValue( nullTarget ); + } + else { + regularValueMappings.add( valueMapping ); + } + + if ( THROW_EXCEPTION.equals( valueMapping.getTarget() ) ) { + hasAtLeastOneExceptionValue = true; + } + } + } + + String getValue(ValueMappingOptions valueMapping) { + return NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget(); + } + } + + private ValueMappingMethod(Method method, + List annotations, + List enumMappings, + String nullTarget, + String defaultTarget, + Type unexpectedValueMappingException, + List beforeMappingMethods, + List afterMappingMethods, + VersionInformation versionInformation) { + super( method, beforeMappingMethods, afterMappingMethods ); + this.valueMappings = enumMappings; + this.nullTarget = new MappingEntry( null, nullTarget ); + this.defaultTarget = new MappingEntry( null, defaultTarget != null ? defaultTarget : THROW_EXCEPTION ); + this.unexpectedValueMappingException = unexpectedValueMappingException; + this.overridden = method.overridesMethod(); + this.annotations = annotations; + this.versionInformation = versionInformation; + } + + public boolean isDefaultTargetRequired() { + if ( !versionInformation.isSourceVersionAtLeast14() ) { + return true; + } + + Type sourceType = getSourceParameter().getType(); + if ( !sourceType.isEnumType() ) { + return true; + } + + return sourceType.getEnumConstants().size() != getValueMappings().size(); + } + + @Override + public Set getImportTypes() { + Set importTypes = super.getImportTypes(); + + if ( unexpectedValueMappingException != null && !unexpectedValueMappingException.isJavaLangType() ) { + if ( ( isDefaultTargetRequired() && defaultTarget.isTargetAsException() ) || + nullTarget.isTargetAsException() || + hasMappingWithTargetAsException() ) { + importTypes.addAll( unexpectedValueMappingException.getImportTypes() ); + } + } + for ( Annotation annotation : annotations ) { + importTypes.addAll( annotation.getImportTypes() ); + } + return importTypes; + } + + protected boolean hasMappingWithTargetAsException() { + return getValueMappings() + .stream() + .anyMatch( MappingEntry::isTargetAsException ); + } + + public List getValueMappings() { + return valueMappings; + } + + public MappingEntry getDefaultTarget() { + return defaultTarget; + } + + public MappingEntry getNullTarget() { + return nullTarget; + } + + public VersionInformation getVersionInformation() { + return versionInformation; + } + + public Type getUnexpectedValueMappingException() { + return unexpectedValueMappingException; + } + + public Parameter getSourceParameter() { + return first( getSourceParameters() ); + } + + public boolean isOverridden() { + return overridden; + } + + public List getAnnotations() { + return annotations; + } + + public static class MappingEntry { + private final String source; + private final String target; + private boolean targetAsException = false; + + MappingEntry(String source, String target) { + this.source = source; + if ( !NULL.equals( target ) ) { + this.target = target; + if ( THROW_EXCEPTION.equals( target ) ) { + this.targetAsException = true; + } + } + else { + this.target = null; + } + } + + public boolean isTargetAsException() { + return targetAsException; + } + + public String getSource() { + return source; + } + + public String getTarget() { + return target; + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/VirtualMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/VirtualMappingMethod.java deleted file mode 100644 index e2bed2d3f8..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/VirtualMappingMethod.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model; - -import java.util.Set; - -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; - -/** - * A mapping method which is not based on an actual method declared in the original mapper interface but is added as - * private method to map a certain source/target type combination. Based on a {@link BuiltInMethod}. - * - * @author Gunnar Morling - */ -public class VirtualMappingMethod extends MappingMethod { - - private final String templateName; - private final Set importTypes; - - public VirtualMappingMethod(BuiltInMethod method) { - super( method ); - this.importTypes = method.getImportTypes(); - this.templateName = getTemplateNameForClass( method.getClass() ); - } - - @Override - public String getTemplateName() { - return templateName; - } - - @Override - public Set getImportTypes() { - return importTypes; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ( ( templateName == null ) ? 0 : templateName.hashCode() ); - return result; - } - - @Override - public boolean equals(Object obj) { - if ( this == obj ) { - return true; - } - if ( obj == null ) { - return false; - } - if ( getClass() != obj.getClass() ) { - return false; - } - VirtualMappingMethod other = (VirtualMappingMethod) obj; - if ( templateName == null ) { - if ( other.templateName != null ) { - return false; - } - } - else if ( !templateName.equals( other.templateName ) ) { - return false; - } - return true; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java new file mode 100644 index 0000000000..f242b51f47 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/AnnotationElement.java @@ -0,0 +1,130 @@ +/* + * 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.model.annotation; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Represents an annotation element. + * + * @author Ben Zegveld + */ +public class AnnotationElement extends ModelElement { + public enum AnnotationElementType { + BOOLEAN, BYTE, CHARACTER, CLASS, DOUBLE, ENUM, FLOAT, INTEGER, LONG, SHORT, STRING + } + + private final String elementName; + private final List values; + private final AnnotationElementType type; + + public AnnotationElement(AnnotationElementType type, List values) { + this( type, null, values ); + } + + public AnnotationElement(AnnotationElementType type, String elementName, List values) { + this.type = type; + this.elementName = elementName; + this.values = values; + } + + public String getElementName() { + return elementName; + } + + public List getValues() { + return values; + } + + @Override + public Set getImportTypes() { + Set importTypes = null; + for ( Object value : values ) { + if ( value instanceof ModelElement ) { + if ( importTypes == null ) { + importTypes = new HashSet<>(); + } + importTypes.addAll( ( (ModelElement) value ).getImportTypes() ); + } + } + + return importTypes == null ? Collections.emptySet() : importTypes; + } + + public boolean isBoolean() { + return type == AnnotationElementType.BOOLEAN; + } + + public boolean isByte() { + return type == AnnotationElementType.BYTE; + } + + public boolean isCharacter() { + return type == AnnotationElementType.CHARACTER; + } + + public boolean isClass() { + return type == AnnotationElementType.CLASS; + } + + public boolean isDouble() { + return type == AnnotationElementType.DOUBLE; + } + + public boolean isEnum() { + return type == AnnotationElementType.ENUM; + } + + public boolean isFloat() { + return type == AnnotationElementType.FLOAT; + } + + public boolean isInteger() { + return type == AnnotationElementType.INTEGER; + } + + public boolean isLong() { + return type == AnnotationElementType.LONG; + } + + public boolean isShort() { + return type == AnnotationElementType.SHORT; + } + + public boolean isString() { + return type == AnnotationElementType.STRING; + } + + @Override + public int hashCode() { + return Objects.hash( elementName, type, values ); + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + AnnotationElement other = (AnnotationElement) obj; + return Objects.equals( elementName, other.elementName ) + && type == other.type + && Objects.equals( values, other.values ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.java new file mode 100644 index 0000000000..de87f32525 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.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.model.annotation; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.Type; + +public class EnumAnnotationElementHolder extends ModelElement { + + private final Type enumClass; + private final String name; + + public EnumAnnotationElementHolder(Type enumClass, String name) { + this.enumClass = enumClass; + this.name = name; + } + + public Type getEnumClass() { + return enumClass; + } + + public String getName() { + return name; + } + + @Override + public Set getImportTypes() { + return enumClass.getImportTypes(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java index 410b94f834..13dfe832c9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AdderWrapper.java @@ -1,29 +1,21 @@ -/** - * Copyright 2012-2015 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.model.assignment; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Nouns; + +import static org.mapstruct.ap.internal.util.Collections.first; /** * Wraps the assignment in a target setter. @@ -33,24 +25,35 @@ public class AdderWrapper extends AssignmentWrapper { private final List thrownTypesToExclude; - private final String sourceReference; - private final Type sourceType; + private final Type adderType; - public AdderWrapper( - Assignment decoratedAssignment, - List thrownTypesToExclude, - String sourceReference, - Type sourceType) { - super( decoratedAssignment ); + public AdderWrapper( Assignment rhs, + List thrownTypesToExclude, + boolean fieldAssignment, + String adderIteratorName ) { + super( rhs, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; - this.sourceReference = sourceReference; - this.sourceType = sourceType; + // a method local var has been added earlier. + + + // localVar is iteratorVariable + String desiredName = Nouns.singularize( adderIteratorName ); + rhs.setSourceLoopVarName( rhs.createUniqueVarName( desiredName ) ); + if ( getSourceType().isCollectionType() ) { + adderType = first( getSourceType().determineTypeArguments( Collection.class ) ); + } + else if ( getSourceType().isArrayType() ) { + adderType = getSourceType().getComponentType(); + } + else { // iterable + adderType = first( getSourceType().determineTypeArguments( Iterable.class ) ); + } } @Override public List getThrownTypes() { List parentThrownTypes = super.getThrownTypes(); - List result = new ArrayList( parentThrownTypes ); + List result = new ArrayList<>( parentThrownTypes ); for ( Type thrownTypeToExclude : thrownTypesToExclude ) { for ( Type parentExceptionType : parentThrownTypes ) { if ( parentExceptionType.isAssignableTo( thrownTypeToExclude ) ) { @@ -61,25 +64,27 @@ public List getThrownTypes() { return result; } - public Type getSourceType() { - return sourceType; + public Type getAdderType() { + return adderType; } - @Override - public String getSourceReference() { - return sourceReference; + public boolean isIncludeSourceNullCheck() { + return true; + } + + public boolean isSetExplicitlyToNull() { + return false; + } + + public boolean isSetExplicitlyToDefault() { + return false; } @Override public Set getImportTypes() { - Set imported = new HashSet(); - imported.addAll( super.getImportTypes() ); - imported.add( sourceType ); + Set imported = new HashSet<>( super.getImportTypes() ); + imported.add( adderType.getTypeBound() ); return imported; } - public String getIteratorReference() { - return getAssignment().getSourceReference(); - } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.java index 41618a8e5e..6b623f7e39 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.java @@ -1,33 +1,15 @@ -/** - * Copyright 2012-2015 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.model.assignment; -import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.Set; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.util.Strings; - -import static org.mapstruct.ap.internal.util.Strings.decapitalize; -import static org.mapstruct.ap.internal.util.Strings.getSaveVariableName; /** * Decorates the assignment as a Map or Collection constructor @@ -36,28 +18,43 @@ */ public class ArrayCopyWrapper extends AssignmentWrapper { - private final String targetPropertyName; private final Type arraysType; private final Type targetType; - - public ArrayCopyWrapper(Assignment decoratedAssignment, String targetPropertyName, Type arraysType, - Type targetType, Collection existingVariableNames ) { - super( decoratedAssignment ); - this.targetPropertyName = Strings.getSaveVariableName( targetPropertyName, existingVariableNames ); + private final boolean setExplicitlyToNull; + private final boolean setExplicitlyToDefault; + + public ArrayCopyWrapper(Assignment rhs, + String targetPropertyName, + Type arraysType, + Type targetType, + boolean fieldAssignment, + boolean setExplicitlyToNull, + boolean setExplicitlyToDefault) { + super( rhs, fieldAssignment ); this.arraysType = arraysType; this.targetType = targetType; + rhs.setSourceLocalVarName( rhs.createUniqueVarName( targetPropertyName ) ); + this.setExplicitlyToDefault = setExplicitlyToDefault; + this.setExplicitlyToNull = setExplicitlyToNull; } @Override public Set getImportTypes() { - Set imported = new HashSet(); - imported.addAll( getAssignment().getImportTypes() ); + Set imported = new HashSet<>( getAssignment().getImportTypes() ); imported.add( arraysType ); imported.add( targetType ); return imported; } - public String getLocalVarName() { - return getSaveVariableName( decapitalize( targetPropertyName ), Collections.emptyList() ); + public boolean isIncludeSourceNullCheck() { + return true; + } + + public boolean isSetExplicitlyToNull() { + return setExplicitlyToNull; + } + + public boolean isSetExplicitlyToDefault() { + return setExplicitlyToDefault; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/Assignment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/Assignment.java deleted file mode 100644 index 64c11d2506..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/Assignment.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.assignment; - -import java.util.List; -import java.util.Set; - -import org.mapstruct.ap.internal.model.common.Type; - -/** - * Assignment represents all kind of manners a source can be assigned to a target. - * - * @author Sjaak Derksen - */ -public interface Assignment { - - public static enum AssignmentType { - /** assignment is direct */ - DIRECT, - /** assignment is type converted */ - TYPE_CONVERTED, - /** assignment is mapped (builtin/custom) */ - MAPPED, - /** 2 mapping methods (builtin/custom) are applied to get the target */ - MAPPED_TWICE, - /** assignment is first mapped (builtin/custom), then the result is type converted */ - MAPPED_TYPE_CONVERTED, - /** assignment is first type converted, and then mapped (builtin/custom) */ - TYPE_CONVERTED_MAPPED - } - - /** - * returns all types required as import by the assignment statement. - * - * @return imported types - */ - Set getImportTypes(); - - /** - * returns all types exception types thrown by this assignment. - * - * @return exceptions thrown - */ - List getThrownTypes(); - - /** - * An assignment in itself can wrap another assignment. E.g.: - *

      - *
    • a MethodReference can wrap a TypeConversion, another MethodReference and ultimately a Simple
    • - *
    • a TypeConversion can wrap a MethodReference, and ultimately a Simple
    • - *
    - * - * @param assignment the assignment to set - */ - void setAssignment(Assignment assignment); - - /** - * the source reference being a source-getter, a constant, etc. - * - * @return source reference - */ - String getSourceReference(); - - /** - * Returns whether the type of assignment - * - * @return {@link AssignmentType} - */ - AssignmentType getType(); - - - boolean isUpdateMethod(); -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AssignmentWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AssignmentWrapper.java index 39d5252629..54885012a9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AssignmentWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/AssignmentWrapper.java @@ -1,27 +1,16 @@ -/** - * Copyright 2012-2015 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.model.assignment; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; /** @@ -32,9 +21,11 @@ public abstract class AssignmentWrapper extends ModelElement implements Assignment { private final Assignment decoratedAssignment; + protected final boolean fieldAssignment; - public AssignmentWrapper( Assignment decoratedAssignment ) { + public AssignmentWrapper( Assignment decoratedAssignment, boolean fieldAssignment ) { this.decoratedAssignment = decoratedAssignment; + this.fieldAssignment = fieldAssignment; } @Override @@ -61,13 +52,67 @@ public String getSourceReference() { return decoratedAssignment.getSourceReference(); } + @Override + public boolean isSourceReferenceParameter() { + return decoratedAssignment.isSourceReferenceParameter(); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return decoratedAssignment.getSourcePresenceCheckerReference(); + } + + @Override + public Type getSourceType() { + return decoratedAssignment.getSourceType(); + } + + @Override + public String getSourceLocalVarName() { + return decoratedAssignment.getSourceLocalVarName(); + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + decoratedAssignment.setSourceLocalVarName( sourceLocalVarName ); + } + + @Override + public String getSourceLoopVarName() { + return decoratedAssignment.getSourceLoopVarName(); + } + + @Override + public void setSourceLoopVarName(String sourceLoopVarName) { + decoratedAssignment.setSourceLoopVarName( sourceLoopVarName ); + } + + @Override + public String getSourceParameterName() { + return decoratedAssignment.getSourceParameterName(); + } + @Override public AssignmentType getType() { return decoratedAssignment.getType(); } @Override - public boolean isUpdateMethod() { - return decoratedAssignment.isUpdateMethod(); + public boolean isCallingUpdateMethod() { + return decoratedAssignment.isCallingUpdateMethod(); + } + + @Override + public String createUniqueVarName(String desiredName ) { + return decoratedAssignment.createUniqueVarName( desiredName ); + } + + /** + * + * @return {@code true} if the wrapper is for field assignment + */ + public boolean isFieldAssignment() { + return fieldAssignment; } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java new file mode 100644 index 0000000000..eaa2fe68ff --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.java @@ -0,0 +1,40 @@ +/* + * 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.model.assignment; + +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Decorates the assignment as an {@link Enum} constant access. + * + * @author Sjaak Derksen + */ +public class EnumConstantWrapper extends AssignmentWrapper { + + private final Type enumType; + + public EnumConstantWrapper(Assignment decoratedAssignment, Type enumType ) { + super( decoratedAssignment, false ); + this.enumType = enumType; + } + + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( getAssignment().getImportTypes() ); + imported.add( enumType ); + return imported; + } + + @Override + public String toString() { + return enumType.getName() + "." + getAssignment(); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java new file mode 100644 index 0000000000..ce347321c9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.java @@ -0,0 +1,81 @@ +/* + * 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.model.assignment; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; + +import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS; +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; + +/** + * This wrapper handles the situation where an assignment is done for an update method. + * + * In case of a pre-existing target the wrapper checks if there is a collection or map initialized on the target bean + * (not null). If so it uses the addAll (for collections) or putAll (for maps). The collection / map is cleared in case + * of a pre-existing target {@link org.mapstruct.MappingTarget }before adding the source entries. + * + * If there is no pre-existing target, or the target Collection / Map is not initialized (null) the setter is used to + * create a new Collection / Map with the copy constructor. + * + * @author Sjaak Derksen + */ +public class ExistingInstanceSetterWrapperForCollectionsAndMaps + extends SetterWrapperForCollectionsAndMapsWithNullCheck { + + private final NullValuePropertyMappingStrategyGem nvpms; + private final NullValueCheckStrategyGem nvcs; + private final Type targetType; + + public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + NullValueCheckStrategyGem nvcs, + NullValuePropertyMappingStrategyGem nvpms, + TypeFactory typeFactory, + boolean fieldAssignment) { + + super( + decoratedAssignment, + thrownTypesToExclude, + targetType, + typeFactory, + fieldAssignment + ); + this.nvcs = nvcs; + this.nvpms = nvpms; + this.targetType = targetType; + } + + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( super.getImportTypes() ); + if ( isMapNullToDefault() && ( targetType.getImplementationType() != null ) ) { + imported.addAll( getNewInstance().getImportTypes() ); + } + return imported; + } + + public boolean isIncludeElseBranch() { + return nvcs != ALWAYS && nvpms != IGNORE; + } + + public boolean isMapNullToDefault() { + return nvpms == NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT; + } + + public boolean isMapNullToClear() { + return nvpms == NullValuePropertyMappingStrategyGem.CLEAR; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java index 14873b637c..e1c0c8cb20 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.java @@ -1,37 +1,25 @@ -/** - * Copyright 2012-2015 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.model.assignment; -import java.util.ArrayList; -import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE; /** * This wrapper handles the situation were an assignment must be done via a target getter method because there * is no setter available. * - * The wrapper checks if there is an collection or map initialized on the target bean (not null). If so it uses the + * The wrapper checks if there is a collection or map initialized on the target bean (not null). If so it uses the * addAll (for collections) or putAll (for maps). The collection / map is cleared in case of a pre-existing target * {@link org.mapstruct.MappingTarget }before adding the source entries. The goal is that the same collection / map * is used as target. @@ -40,53 +28,47 @@ * * @author Sjaak Derksen */ -public class GetterWrapperForCollectionsAndMaps extends AssignmentWrapper { - - private final List thrownTypesToExclude; - private final Type localVarType; - private final String localVarName; +public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAndMaps { + private final boolean ignoreMapNull; + public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + boolean fieldAssignment) { + this( decoratedAssignment, thrownTypesToExclude, targetType, null, fieldAssignment ); + } - public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, List thrownTypesToExclude, - Type localVarType, Collection existingVariableNames) { - super( decoratedAssignment ); - this.thrownTypesToExclude = thrownTypesToExclude; - this.localVarType = localVarType; - this.localVarName = Strings.getSaveVariableName( "target" + localVarType.getName(), existingVariableNames ); - existingVariableNames.add( localVarName ); - } + /** + * @param decoratedAssignment source RHS + * @param thrownTypesToExclude set of types to exclude from re-throwing + * @param targetType the target type + * @param fieldAssignment true when this the assignment is to a field rather than via accessors + */ + public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + NullValuePropertyMappingStrategyGem nvpms, + boolean fieldAssignment) { - @Override - public List getThrownTypes() { - List parentThrownTypes = super.getThrownTypes(); - List result = new ArrayList( parentThrownTypes ); - for ( Type thrownTypeToExclude : thrownTypesToExclude ) { - for ( Type parentThrownType : parentThrownTypes ) { - if ( parentThrownType.isAssignableTo( thrownTypeToExclude ) ) { - result.remove( parentThrownType ); - } - } - } - return result; + super( + decoratedAssignment, + thrownTypesToExclude, + targetType, + fieldAssignment + ); + this.ignoreMapNull = nvpms == IGNORE; } @Override public Set getImportTypes() { - Set imported = new HashSet(); - imported.addAll( super.getImportTypes() ); - imported.add( localVarType ); /* is a local var */ + Set imported = new HashSet<>( super.getImportTypes() ); + if ( getSourcePresenceCheckerReference() == null ) { + imported.addAll( getNullCheckLocalVarType().getImportTypes() ); + } return imported; } - /** - * @return the targetType - */ - public Type getLocalVarType() { - return localVarType; - } - - public String getLocalVarName() { - return localVarName; + public boolean isIgnoreMapNull() { + return ignoreMapNull; } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.java new file mode 100644 index 0000000000..e629ae35f1 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.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.ap.internal.model.assignment; + +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * Wraps the assignment in a Function to be used in Java 8 map methods + * + * @author Filip Hrisafov + */ +public class Java8FunctionWrapper extends AssignmentWrapper { + + private final Type functionType; + + public Java8FunctionWrapper(Assignment decoratedAssignment) { + this( decoratedAssignment, null ); + } + + public Java8FunctionWrapper(Assignment decoratedAssignment, Type functionType) { + super( decoratedAssignment, false ); + this.functionType = functionType; + } + + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( super.getImportTypes() ); + if ( isDirectAssignment() && functionType != null ) { + imported.add( functionType ); + } + return imported; + } + + /** + * + * @return {@code true} if the wrapped assignment is + * {@link Assignment.AssignmentType#DIRECT}, {@code false} otherwise + */ + public boolean isDirectAssignment() { + return getAssignment().getType() == AssignmentType.DIRECT; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.java index d89b120add..1287ab6cd6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.java @@ -1,26 +1,16 @@ -/** - * Copyright 2012-2015 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.model.assignment; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; /** @@ -31,16 +21,19 @@ public class LocalVarWrapper extends AssignmentWrapper { private final List thrownTypesToExclude; + private final Type targetType; - public LocalVarWrapper( Assignment decoratedAssignment, List thrownTypesToExclude ) { - super( decoratedAssignment ); + public LocalVarWrapper(Assignment decoratedAssignment, List thrownTypesToExclude, Type targetType, + boolean fieldAssignment) { + super( decoratedAssignment, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; + this.targetType = targetType; } @Override public List getThrownTypes() { List parentThrownTypes = super.getThrownTypes(); - List result = new ArrayList( parentThrownTypes ); + List result = new ArrayList<>( parentThrownTypes ); for ( Type thrownTypeToExclude : thrownTypesToExclude ) { for ( Type parentThrownType : parentThrownTypes ) { if ( parentThrownType.isAssignableTo( thrownTypeToExclude ) ) { @@ -50,4 +43,13 @@ public List getThrownTypes() { } return result; } + + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( getAssignment().getImportTypes() ); + imported.add( targetType ); + imported.addAll( targetType.getTypeParameters() ); + return imported; + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewCollectionOrMapWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewCollectionOrMapWrapper.java deleted file mode 100644 index a715d10523..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewCollectionOrMapWrapper.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.assignment; - -import java.util.HashSet; -import java.util.Set; - -import org.mapstruct.ap.internal.model.common.Type; - -/** - * Decorates the assignment as a Map or Collection constructor - * - * @author Sjaak Derksen - */ -public class NewCollectionOrMapWrapper extends AssignmentWrapper { - - private final Set implementationTypes; - - public NewCollectionOrMapWrapper(Assignment decoratedAssignment, Set implementationTypes) { - super( decoratedAssignment ); - this.implementationTypes = implementationTypes; - } - - @Override - public Set getImportTypes() { - Set imported = new HashSet(); - imported.addAll( getAssignment().getImportTypes() ); - imported.addAll( implementationTypes ); - return imported; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.java new file mode 100644 index 0000000000..f0e23470a7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.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.ap.internal.model.assignment; + +import java.util.List; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; + +/** + * This wrapper handles the situation where an assignment is done via the setter, while creating the collection or map + * using a no-args constructor. + * + * @author Ben Zegveld + */ +public class NewInstanceSetterWrapperForCollectionsAndMaps extends SetterWrapperForCollectionsAndMapsWithNullCheck { + + private String instanceVar; + + public NewInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + TypeFactory typeFactory, + boolean fieldAssignment) { + + super( + decoratedAssignment, + thrownTypesToExclude, + targetType, + typeFactory, + fieldAssignment + ); + this.instanceVar = decoratedAssignment.createUniqueVarName( targetType.getName() ); + } + + public String getInstanceVar() { + return instanceVar; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.java deleted file mode 100644 index 043ec86a1b..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.assignment; - -/** - * Wraps the assignment in a null check. - * - * @author Sjaak Derksen - */ -public class NullCheckWrapper extends AssignmentWrapper { - - public NullCheckWrapper( Assignment decoratedAssignment ) { - super( decoratedAssignment ); - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.java new file mode 100644 index 0000000000..6c405c871d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.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.model.assignment; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; + +/** + * Decorates the assignment as an {@link java.util.Optional#get()} call. + * + * @author Filip Hrisafov + */ +public class OptionalGetWrapper extends AssignmentWrapper { + + private final Type optionalType; + + public OptionalGetWrapper(Assignment decoratedAssignment, Type optionalType) { + super( decoratedAssignment, false ); + this.optionalType = optionalType; + } + + public Type getOptionalType() { + return optionalType; + } + + @Override + public String toString() { + if ( optionalType.getFullyQualifiedName().equals( "java.util.Optional" ) ) { + return getAssignment() + ".get()"; + } + return getAssignment() + ".getAs" + Strings.capitalize( optionalType.getOptionalBaseType().getName() ) + "()"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.java new file mode 100644 index 0000000000..76e2c19fa7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.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.ap.internal.model.assignment; + +import org.mapstruct.ap.internal.model.common.Assignment; + +/** + * Decorates an assignment as a return variable. + * + * @author Ben Zegveld + */ +public class ReturnWrapper extends AssignmentWrapper { + + public ReturnWrapper(Assignment decoratedAssignment) { + super( decoratedAssignment, false ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java index 612d74ebb5..94b031c622 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java @@ -1,26 +1,16 @@ -/** - * Copyright 2012-2015 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.model.assignment; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; /** @@ -31,16 +21,44 @@ public class SetterWrapper extends AssignmentWrapper { private final List thrownTypesToExclude; + private final boolean includeSourceNullCheck; + private final boolean setExplicitlyToNull; + private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; + private final Type nullCastType; - public SetterWrapper( Assignment decoratedAssignment, List thrownTypesToExclude ) { - super( decoratedAssignment ); + public SetterWrapper(Assignment rhs, + List thrownTypesToExclude, + boolean fieldAssignment, + boolean includeSourceNullCheck, + boolean setExplicitlyToNull, + boolean setExplicitlyToDefault, + boolean mustCastForNull, + Type nullCastType) { + + super( rhs, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; + this.includeSourceNullCheck = includeSourceNullCheck; + this.setExplicitlyToDefault = setExplicitlyToDefault; + this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; + this.nullCastType = nullCastType; + } + + public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fieldAssignment ) { + super( rhs, fieldAssignment ); + this.thrownTypesToExclude = thrownTypesToExclude; + this.includeSourceNullCheck = false; + this.setExplicitlyToNull = false; + this.setExplicitlyToDefault = false; + this.mustCastForNull = false; + this.nullCastType = null; } @Override public List getThrownTypes() { List parentThrownTypes = super.getThrownTypes(); - List result = new ArrayList( parentThrownTypes ); + List result = new ArrayList<>( parentThrownTypes ); for ( Type thrownTypeToExclude : thrownTypesToExclude ) { for ( Type parentThrownType : parentThrownTypes ) { if ( parentThrownType.isAssignableTo( thrownTypeToExclude ) ) { @@ -51,4 +69,28 @@ public List getThrownTypes() { return result; } + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( super.getImportTypes() ); + if ( isSetExplicitlyToNull() && isMustCastForNull() ) { + imported.add( nullCastType ); + } + return imported; + } + + public boolean isSetExplicitlyToNull() { + return setExplicitlyToNull; + } + + public boolean isSetExplicitlyToDefault() { + return setExplicitlyToDefault; + } + + public boolean isIncludeSourceNullCheck() { + return includeSourceNullCheck; + } + + public boolean isMustCastForNull() { + return mustCastForNull; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.java index 2b6dafa16d..9f84aeeff8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.java @@ -1,72 +1,32 @@ -/** - * Copyright 2012-2015 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.model.assignment; -import java.util.HashSet; -import java.util.Set; - -import javax.lang.model.element.ExecutableElement; +import java.util.List; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.util.Executables; /** - * This wrapper handles the situation were an assignment is done via the setter. - * - * In case of a pre-existing target the wrapper checks if there is an collection or map initialized on the target bean - * (not null). If so it uses the addAll (for collections) or putAll (for maps). The collection / map is cleared in case - * of a pre-existing target {@link org.mapstruct.MappingTarget }before adding the source entries. - * - * If there is no pre-existing target, or the target Collection / Map is not initialized (null) the setter is used to - * create a new Collection / Map with the copy constructor. + * This wrapper handles the situation where an assignment is done via the setter, without doing anything special. * * @author Sjaak Derksen */ -public class SetterWrapperForCollectionsAndMaps extends AssignmentWrapper { - - private final String targetGetterName; - private final Assignment newCollectionOrMapAssignment; +public class SetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAndMaps { public SetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, - ExecutableElement targetSetter, - Assignment newCollectionOrMapAssignment) { - super( decoratedAssignment ); - - this.targetGetterName = Executables.getCollectionGetterName( targetSetter ); - this.newCollectionOrMapAssignment = newCollectionOrMapAssignment; - } - - public String getTargetGetterName() { - return targetGetterName; - } - - public Assignment getNewCollectionOrMapAssignment() { - return newCollectionOrMapAssignment; - } - - @Override - public Set getImportTypes() { - Set imported = new HashSet(); - imported.addAll( getAssignment().getImportTypes() ); - if ( newCollectionOrMapAssignment != null ) { - imported.addAll( newCollectionOrMapAssignment.getImportTypes() ); - } - return imported; + List thrownTypesToExclude, + Type targetType, + boolean fieldAssignment) { + + super( + decoratedAssignment, + thrownTypesToExclude, + targetType, + fieldAssignment + ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java new file mode 100644 index 0000000000..8ef71f7c0d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.java @@ -0,0 +1,75 @@ +/* + * 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.model.assignment; + +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.NewInstanceCreation; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; + +import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT; + +/** + * This wrapper handles the situation where an assignment is done via the setter and a null check is needed. + * This is needed when a direct assignment is used, or if the user has chosen the appropriate strategy + * + * @author Sjaak Derksen + */ +public class SetterWrapperForCollectionsAndMapsWithNullCheck extends WrapperForCollectionsAndMaps { + + private final Type targetType; + private final TypeFactory typeFactory; + private final NewInstanceCreation newInstance; + + public SetterWrapperForCollectionsAndMapsWithNullCheck(Assignment decoratedAssignment, + List thrownTypesToExclude, + Type targetType, + TypeFactory typeFactory, + boolean fieldAssignment) { + super( + decoratedAssignment, + thrownTypesToExclude, + targetType, + fieldAssignment + ); + this.targetType = targetType; + this.typeFactory = typeFactory; + this.newInstance = NewInstanceCreation.forType( targetType ); + } + + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( super.getImportTypes() ); + if ( isDirectAssignment() ) { + imported.addAll( newInstance.getImportTypes() ); + if ( isEnumSet() ) { + imported.add( typeFactory.getType( EnumSet.class ) ); + } + } + if ( isDirectAssignment() || getSourcePresenceCheckerReference() == null ) { + imported.addAll( getNullCheckLocalVarType().getImportTypes() ); + } + return imported; + } + + public NewInstanceCreation getNewInstance() { + return newInstance; + } + + public boolean isDirectAssignment() { + return getType() == DIRECT; + } + + public boolean isEnumSet() { + return targetType.isEnumSet(); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/StreamAdderWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/StreamAdderWrapper.java new file mode 100644 index 0000000000..21a028c79f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/StreamAdderWrapper.java @@ -0,0 +1,78 @@ +/* + * 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.model.assignment; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Nouns; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * Wraps the assignment in a target setter. + * + * @author Sebastian Haberey + */ +public class StreamAdderWrapper extends AssignmentWrapper { + + private final List thrownTypesToExclude; + private final Type adderType; + + public StreamAdderWrapper(Assignment rhs, + List thrownTypesToExclude, + boolean fieldAssignment, + String targetPropertyName ) { + super( rhs, fieldAssignment ); + this.thrownTypesToExclude = thrownTypesToExclude; + String desiredName = Nouns.singularize( targetPropertyName ); + rhs.setSourceLocalVarName( rhs.createUniqueVarName( desiredName ) ); + adderType = first( getSourceType().determineTypeArguments( Stream.class ) ); + } + + @Override + public List getThrownTypes() { + List parentThrownTypes = super.getThrownTypes(); + List result = new ArrayList<>( parentThrownTypes ); + for ( Type thrownTypeToExclude : thrownTypesToExclude ) { + for ( Type parentExceptionType : parentThrownTypes ) { + if ( parentExceptionType.isAssignableTo( thrownTypeToExclude ) ) { + result.remove( parentExceptionType ); + } + } + } + return result; + } + + public Type getAdderType() { + return adderType; + } + + public boolean isIncludeSourceNullCheck() { + return true; + } + + public boolean isSetExplicitlyToNull() { + return false; + } + + public boolean isSetExplicitlyToDefault() { + return false; + } + + @Override + public Set getImportTypes() { + Set imported = new HashSet<>( super.getImportTypes() ); + imported.add( adderType.getTypeBound() ); + return imported; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.java deleted file mode 100644 index ff3e3b6134..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.assignment; - -/** - * Wraps an update-assignment in a null check and sets the target property to {@code null}, if the source is - * {@code null}. - * - * @author Andreas Gudian - */ -public class UpdateNullCheckWrapper extends AssignmentWrapper { - - public UpdateNullCheckWrapper( Assignment decoratedAssignment ) { - super( decoratedAssignment ); - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java index f2fd6833cf..c80b5293c8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.assignment; @@ -23,6 +10,8 @@ import java.util.List; import java.util.Set; +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.NewInstanceCreation; import org.mapstruct.ap.internal.model.common.Type; /** @@ -34,34 +23,35 @@ public class UpdateWrapper extends AssignmentWrapper { private final List thrownTypesToExclude; private final Assignment factoryMethod; - private final Type targetImplementationType; + private final NewInstanceCreation newInstance; + private final boolean includeSourceNullCheck; + private final boolean setExplicitlyToNull; + private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; - public UpdateWrapper(Assignment decoratedAssignment, List thrownTypesToExclude, Assignment factoryMethod, - Type targetImplementationType ) { - super( decoratedAssignment ); + public UpdateWrapper( Assignment decoratedAssignment, + List thrownTypesToExclude, + Assignment factoryMethod, + boolean fieldAssignment, + Type targetType, + boolean includeSourceNullCheck, + boolean setExplicitlyToNull, + boolean setExplicitlyToDefault, + boolean mustCastForNull) { + super( decoratedAssignment, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.factoryMethod = factoryMethod; - this.targetImplementationType = determineImplType( factoryMethod, targetImplementationType ); - } - - private static Type determineImplType(Assignment factoryMethod, Type targetType) { - if ( targetType.getImplementationType() != null ) { - // it's probably a collection or something - return targetType.getImplementationType(); - } - - if ( factoryMethod == null ) { - // no factory method means we create a new instance ourself and thus need to import the type - return targetType; - } - - return null; + this.newInstance = ( factoryMethod == null ) ? NewInstanceCreation.forType( targetType ) : null; + this.includeSourceNullCheck = includeSourceNullCheck; + this.setExplicitlyToDefault = setExplicitlyToDefault; + this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; } @Override public List getThrownTypes() { List parentThrownTypes = super.getThrownTypes(); - List result = new ArrayList( parentThrownTypes ); + List result = new ArrayList<>( parentThrownTypes ); for ( Type thrownTypeToExclude : thrownTypesToExclude ) { for ( Type parentThrownType : parentThrownTypes ) { if ( parentThrownType.isAssignableTo( thrownTypeToExclude ) ) { @@ -74,10 +64,12 @@ public List getThrownTypes() { @Override public Set getImportTypes() { - Set imported = new HashSet(); - imported.addAll( super.getImportTypes() ); - if ( targetImplementationType != null ) { - imported.add( targetImplementationType ); + Set imported = new HashSet<>( super.getImportTypes() ); + if ( factoryMethod != null ) { + imported.addAll( factoryMethod.getImportTypes() ); + } + if ( newInstance != null ) { + imported.addAll( newInstance.getImportTypes() ); } return imported; } @@ -86,4 +78,23 @@ public Assignment getFactoryMethod() { return factoryMethod; } + public NewInstanceCreation getNewInstance() { + return newInstance; + } + + public boolean isIncludeSourceNullCheck() { + return includeSourceNullCheck; + } + + public boolean isSetExplicitlyToNull() { + return setExplicitlyToNull; + } + + public boolean isSetExplicitlyToDefault() { + return setExplicitlyToDefault; + } + + public boolean isMustCastForNull() { + return mustCastForNull; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/WrapperForCollectionsAndMaps.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/WrapperForCollectionsAndMaps.java new file mode 100644 index 0000000000..e8b1f88457 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/WrapperForCollectionsAndMaps.java @@ -0,0 +1,64 @@ +/* + * 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.model.assignment; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.internal.model.common.Assignment; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * This is the base class for the {@link GetterWrapperForCollectionsAndMaps} and + * {@link SetterWrapperForCollectionsAndMaps} + * + * @author Sjaak Derksen + */ +public class WrapperForCollectionsAndMaps extends AssignmentWrapper { + + private final List thrownTypesToExclude; + private final String nullCheckLocalVarName; + private final Type nullCheckLocalVarType; + + public WrapperForCollectionsAndMaps(Assignment rhs, + List thrownTypesToExclude, + Type targetType, + boolean fieldAssignment) { + + super( rhs, fieldAssignment ); + + this.thrownTypesToExclude = thrownTypesToExclude; + if ( rhs.getType() == AssignmentType.DIRECT && rhs.getSourceType() != null ) { + this.nullCheckLocalVarType = rhs.getSourceType(); + } + else { + this.nullCheckLocalVarType = targetType; + } + this.nullCheckLocalVarName = rhs.createUniqueVarName( nullCheckLocalVarType.getName() ); + } + + @Override + public List getThrownTypes() { + List parentThrownTypes = super.getThrownTypes(); + List result = new ArrayList<>( parentThrownTypes ); + for ( Type thrownTypeToExclude : thrownTypesToExclude ) { + for ( Type parentThrownType : parentThrownTypes ) { + if ( parentThrownType.isAssignableTo( thrownTypeToExclude ) ) { + result.remove( parentThrownType ); + } + } + } + return result; + } + + public String getNullCheckLocalVarName() { + return nullCheckLocalVarName; + } + + public Type getNullCheckLocalVarType() { + return nullCheckLocalVarType; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/package-info.java index 4f7b466b87..9a68f1f632 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** * Meta-model of assignments. There are currently three types of assignment: diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/AbstractReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/AbstractReference.java new file mode 100644 index 0000000000..72f2b8c8fe --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/AbstractReference.java @@ -0,0 +1,128 @@ +/* + * 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.model.beanmapping; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.util.Collections.first; +import static org.mapstruct.ap.internal.util.Collections.last; + +/** + * Class acts as a common base class for {@link TargetReference} and {@link SourceReference}. + * + * @author sjaak + */ +public abstract class AbstractReference { + + private final Parameter parameter; + private final List propertyEntries; + private final boolean isValid; + + protected AbstractReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { + this.parameter = sourceParameter; + this.propertyEntries = sourcePropertyEntries; + this.isValid = isValid; + } + + public Parameter getParameter() { + return parameter; + } + + public List getPropertyEntries() { + return propertyEntries; + } + + public boolean isValid() { + return isValid; + } + + public List getElementNames() { + List elementNames = new ArrayList<>(); + if ( parameter != null ) { + // only relevant for source properties + elementNames.add( parameter.getName() ); + } + for ( PropertyEntry propertyEntry : propertyEntries ) { + elementNames.add( propertyEntry.getName() ); + } + return elementNames; + } + + /** + * @return the property name on the shallowest nesting level + */ + public PropertyEntry getShallowestProperty() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return first( propertyEntries ); + } + + /** + * @return the property name on the shallowest nesting level + */ + public String getShallowestPropertyName() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return first( propertyEntries ).getName(); + } + + /** + * @return the property name on the deepest nesting level + */ + public PropertyEntry getDeepestProperty() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return last( propertyEntries ); + } + + /** + * @return the property name on the deepest nesting level + */ + public String getDeepestPropertyName() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return last( propertyEntries ).getName(); + } + + public boolean isNested() { + return propertyEntries.size() > 1; + } + + @Override + public String toString() { + + String result = ""; + if ( !isValid ) { + result = "invalid"; + } + else if ( propertyEntries.isEmpty() ) { + if ( parameter != null ) { + result = String.format( "parameter \"%s %s\"", parameter.getType().describe(), parameter.getName() ); + } + } + else if ( propertyEntries.size() == 1 ) { + PropertyEntry propertyEntry = propertyEntries.get( 0 ); + result = String.format( "property \"%s %s\"", propertyEntry.getType().describe(), propertyEntry.getName() ); + } + else { + PropertyEntry lastPropertyEntry = propertyEntries.get( propertyEntries.size() - 1 ); + result = String.format( + "property \"%s %s\"", + lastPropertyEntry.getType().describe(), + Strings.join( getElementNames(), "." ) + ); + } + return result; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java new file mode 100644 index 0000000000..9baa84dc77 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReference.java @@ -0,0 +1,128 @@ +/* + * 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.model.beanmapping; + +import java.util.Objects; + +import org.mapstruct.ap.internal.model.source.MappingOptions; + +/** + * Represents the intermediate (nesting) state of the {@link MappingOptions} in this class. + */ +public class MappingReference { + + private MappingOptions mapping; + + private TargetReference targetReference; + + private SourceReference sourceReference; + + public MappingReference(MappingOptions mapping, TargetReference targetReference, SourceReference sourceReference) { + this.mapping = mapping; + this.targetReference = targetReference; + this.sourceReference = sourceReference; + } + + public MappingOptions getMapping() { + return mapping; + } + + public SourceReference getSourceReference() { + return sourceReference; + } + + public void setSourceReference(SourceReference sourceReference) { + this.sourceReference = sourceReference; + } + + public TargetReference getTargetReference() { + return targetReference; + } + + public MappingReference popTargetReference() { + if ( targetReference != null ) { + TargetReference newTargetReference = targetReference.pop(); + if ( newTargetReference != null ) { + return new MappingReference(mapping, newTargetReference, sourceReference ); + } + } + return null; + } + + public MappingReference popSourceReference() { + if ( sourceReference != null ) { + SourceReference newSourceReference = sourceReference.pop(); + if ( newSourceReference != null ) { + return new MappingReference(mapping, targetReference, newSourceReference ); + } + } + return null; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + MappingReference that = (MappingReference) o; + if ( ".".equals( that.mapping.getTargetName() ) ) { + // target this will never be equal to any other target this or any other. + return false; + } + + if ( !Objects.equals( mapping.getTargetName(), that.mapping.getTargetName() ) ) { + return false; + } + + if ( !Objects.equals( mapping.getConstant(), that.mapping.getConstant() ) ) { + return false; + } + + if ( !Objects.equals( mapping.getJavaExpression(), that.mapping.getJavaExpression() ) ) { + return false; + } + + if ( sourceReference == null ) { + return that.sourceReference == null; + } + + if ( that.sourceReference == null ) { + return false; + } + + + if ( !Objects.equals( sourceReference.getPropertyEntries(), that.sourceReference.getPropertyEntries() ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return Objects.hash( mapping ); + } + + public boolean isValid( ) { + return sourceReference == null || sourceReference.isValid(); + } + + @Override + public String toString() { + String targetRefStr = targetReference.toString(); + String sourceRefStr = "null"; + if ( sourceReference != null ) { + sourceRefStr = sourceReference.toString(); + } + return "MappingReference {" + + "\n sourceReference='" + sourceRefStr + "\'," + + "\n targetReference='" + targetRefStr + "\'," + + "\n}"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java new file mode 100644 index 0000000000..edc5b3157e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java @@ -0,0 +1,163 @@ +/* + * 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.model.beanmapping; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.model.source.MappingOptions; +import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.util.FormattingMessager; + +public class MappingReferences { + + private static final MappingReferences EMPTY = new MappingReferences( Collections.emptySet(), false ); + + private final Set mappingReferences; + private final boolean restrictToDefinedMappings; + private final boolean forForgedMethods; + + public static MappingReferences empty() { + return EMPTY; + } + + public static MappingReferences forSourceMethod(SourceMethod sourceMethod, + Type targetType, + Set targetProperties, + FormattingMessager messager, + TypeFactory typeFactory) { + + Set references = new LinkedHashSet<>(); + + for ( MappingOptions mapping : sourceMethod.getOptions().getMappings() ) { + + // handle source reference + SourceReference sourceReference = new SourceReference.BuilderFromMapping().mapping( mapping ) + .method( sourceMethod ) + .messager( messager ) + .typeFactory( typeFactory ) + .build(); + + // handle target reference + TargetReference targetReference = new TargetReference.Builder().mapping( mapping ) + .method( sourceMethod ) + .messager( messager ) + .typeFactory( typeFactory ) + .targetProperties( targetProperties ) + .targetType( targetType ) + .build(); + + // add when inverse is also valid + MappingReference mappingReference = new MappingReference( mapping, targetReference, sourceReference ); + if ( isValidWhenInversed( mappingReference ) ) { + references.add( mappingReference ); + } + } + return new MappingReferences( references, false ); + } + + public MappingReferences(Set mappingReferences, boolean restrictToDefinedMappings) { + this.mappingReferences = mappingReferences; + this.restrictToDefinedMappings = restrictToDefinedMappings; + this.forForgedMethods = restrictToDefinedMappings; + } + + public MappingReferences(Set mappingReferences, boolean restrictToDefinedMappings, + boolean forForgedMethods) { + this.mappingReferences = mappingReferences; + this.restrictToDefinedMappings = restrictToDefinedMappings; + this.forForgedMethods = forForgedMethods; + } + + public Set getMappingReferences() { + return mappingReferences; + } + + public boolean isRestrictToDefinedMappings() { + return restrictToDefinedMappings; + } + + public boolean isForForgedMethods() { + return forForgedMethods; + } + + /** + * @return all dependencies to other properties the contained mappings are dependent on + */ + public Set collectNestedDependsOn() { + + Set nestedDependsOn = new LinkedHashSet<>(); + for ( MappingReference mapping : getMappingReferences() ) { + nestedDependsOn.addAll( mapping.getMapping().getDependsOn() ); + } + return nestedDependsOn; + } + + /** + * Check there are nested target references for this mapping options. + * + * @return boolean, true if there are nested target references + */ + public boolean hasNestedTargetReferences() { + + for ( MappingReference mappingRef : mappingReferences ) { + TargetReference targetReference = mappingRef.getTargetReference(); + if ( targetReference.isNested() ) { + return true; + } + + } + return false; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( !( o instanceof MappingReferences ) ) { + return false; + } + + MappingReferences that = (MappingReferences) o; + + if ( restrictToDefinedMappings != that.restrictToDefinedMappings ) { + return false; + } + if ( forForgedMethods != that.forForgedMethods ) { + return false; + } + if ( !Objects.equals( mappingReferences, that.mappingReferences ) ) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + return mappingReferences != null ? mappingReferences.hashCode() : 0; + } + + /** + * MapStruct filters automatically inversed invalid methods out. TODO: this is a principle we should discuss! + * @param mappingRef + * @return + */ + private static boolean isValidWhenInversed(MappingReference mappingRef) { + MappingOptions mapping = mappingRef.getMapping(); + if ( mapping.getInheritContext() != null && mapping.getInheritContext().isReversed() ) { + return ( mappingRef.getSourceReference() == null || + mappingRef.getSourceReference().isValid() ); + } + return true; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java new file mode 100644 index 0000000000..a3c8a74e3f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/PropertyEntry.java @@ -0,0 +1,101 @@ +/* + * 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.model.beanmapping; + +import java.util.Arrays; + +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; + +/** + * A PropertyEntry contains information on the name, readAccessor and presenceCheck (for source) + * and return type of property. + */ +public class PropertyEntry { + + private final String[] fullName; + private final ReadAccessor readAccessor; + private final PresenceCheckAccessor presenceChecker; + private final Type type; + + /** + * Constructor used to create {@link TargetReference} property entries from a mapping + * + * @param fullName + * @param readAccessor + * @param type + */ + private PropertyEntry(String[] fullName, ReadAccessor readAccessor, PresenceCheckAccessor presenceChecker, + Type type) { + this.fullName = fullName; + this.readAccessor = readAccessor; + this.presenceChecker = presenceChecker; + this.type = type; + } + + /** + * Constructor used to create {@link SourceReference} property entries from a mapping + * + * @param name name of the property (dot separated) + * @param readAccessor its read accessor + * @param presenceChecker its presence Checker + * @param type type of the property + * @return the property entry for given parameters. + */ + public static PropertyEntry forSourceReference(String[] name, ReadAccessor readAccessor, + PresenceCheckAccessor presenceChecker, Type type) { + return new PropertyEntry( name, readAccessor, presenceChecker, type ); + } + + public String getName() { + return fullName[fullName.length - 1]; + } + + public ReadAccessor getReadAccessor() { + return readAccessor; + } + + public PresenceCheckAccessor getPresenceChecker() { + return presenceChecker; + } + + public Type getType() { + return type; + } + + public String getFullName() { + return Strings.join( Arrays.asList( fullName ), "." ); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + Arrays.deepHashCode( this.fullName ); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + final PropertyEntry other = (PropertyEntry) obj; + return Arrays.deepEquals( this.fullName, other.fullName ); + } + + @Override + public String toString() { + return type + " " + Strings.join( Arrays.asList( fullName ), "." ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java new file mode 100644 index 0000000000..c37b12e311 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -0,0 +1,478 @@ +/* + * 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.model.beanmapping; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.type.DeclaredType; + +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.MappingOptions; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; + +import static org.mapstruct.ap.internal.model.beanmapping.PropertyEntry.forSourceReference; +import static org.mapstruct.ap.internal.util.Collections.last; + +/** + * This class describes the source side of a property mapping. + *

    + * It contains the source parameter, and all individual (nested) property entries. So consider the following + * mapping method: + * + *

    + * @Mapping(target = "propC", source = "in.propA.propB")
    + * TypeB mappingMethod(TypeA in);
    + * 
    + * + * Then: + *
      + *
    • {@code parameter} will describe {@code in}
    • + *
    • {@code propertyEntries[0]} will describe {@code propA}
    • + *
    • {@code propertyEntries[1]} will describe {@code propB}
    • + *
    + * + * After building, {@link #isValid()} will return true when no problems are detected during building. + * + * @author Sjaak Derksen + * @author Filip Hrisafov + */ +public class SourceReference extends AbstractReference { + + /** + * Builds a {@link SourceReference} from an {@code @Mappping}. + */ + public static class BuilderFromMapping { + + private Method method; + private FormattingMessager messager = null; + private TypeFactory typeFactory; + + private boolean isForwarded = false; + private Method templateMethod = null; + private String sourceName; + private AnnotationMirror annotationMirror; + private AnnotationValue sourceAnnotationValue; + + public BuilderFromMapping messager(FormattingMessager messager) { + this.messager = messager; + return this; + } + + public BuilderFromMapping mapping(MappingOptions mapping) { + this.sourceName = mapping.getSourceName(); + this.annotationMirror = mapping.getMirror(); + this.sourceAnnotationValue = mapping.getSourceAnnotationValue(); + if ( mapping.getInheritContext() != null ) { + isForwarded = mapping.getInheritContext().isForwarded(); + templateMethod = mapping.getInheritContext().getTemplateMethod(); + } + return this; + } + + public BuilderFromMapping method(Method method) { + this.method = method; + return this; + } + + public BuilderFromMapping typeFactory(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + return this; + } + + public BuilderFromMapping sourceName(String sourceName) { + this.sourceName = sourceName; + return this; + } + + public SourceReference build() { + + if ( sourceName == null ) { + return null; + } + + Objects.requireNonNull( messager ); + + String sourceNameTrimmed = sourceName.trim(); + if ( !sourceName.equals( sourceNameTrimmed ) ) { + messager.printMessage( + method.getExecutable(), + annotationMirror, + sourceAnnotationValue, + Message.PROPERTYMAPPING_WHITESPACE_TRIMMED, + sourceName, + sourceNameTrimmed + ); + } + + String[] segments = sourceNameTrimmed.split( "\\." ); + + // start with an invalid source reference + SourceReference result = new SourceReference( null, new ArrayList<>( ), false ); + if ( method.getSourceParameters().size() > 1 ) { + Parameter parameter = fetchMatchingParameterFromFirstSegment( segments ); + if ( parameter != null ) { + result = buildFromMultipleSourceParameters( segments, parameter ); + } + } + else { + Parameter parameter = method.getSourceParameters().get( 0 ); + result = buildFromSingleSourceParameters( segments, parameter ); + } + return result; + + } + + /** + * When there is only one source parameters, the first segment name of the property may, or may not match + * the parameter name to avoid ambiguity + * + * consider: {@code Target map( Source1 source1 )} + * entries in a @Mapping#source can be "source1.propx" or just "propx" to be valid + * + * @param segments the segments of @Mapping#source + * @param parameter the one and only parameter + * @return the source reference + */ + private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { + boolean foundEntryMatch; + + boolean allowedMapToBean = false; + if ( segments.length > 0 ) { + if ( parameter.getType().isMapType() ) { + // When the parameter type is a map and the parameter name matches the first segment + // then the first segment should not be treated as a property of the map + allowedMapToBean = !segments[0].equals( parameter.getName() ); + } + } + String[] propertyNames = segments; + List entries = matchWithSourceAccessorTypes( + parameter.getType(), + propertyNames, + allowedMapToBean + ); + foundEntryMatch = ( entries.size() == propertyNames.length ); + + if ( !foundEntryMatch ) { + //Lets see if the expression contains the parameterName, so parameterName.propName1.propName2 + if ( getSourceParameterFromMethodOrTemplate( segments[0] ) != null ) { + propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); + entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames, true ); + foundEntryMatch = ( entries.size() == propertyNames.length ); + } + else { + // segment[0] cannot be attributed to the parameter name. + parameter = null; + } + } + + if ( !foundEntryMatch ) { + reportErrorOnNoMatch( parameter, propertyNames, entries ); + } + + return new SourceReference( parameter, entries, foundEntryMatch ); + } + + /** + * When there are more than one source parameters, the first segment name of the property + * needs to match the parameter name to avoid ambiguity + * + * @param segments the segments of @Mapping#source + * @param parameter the relevant source parameter + * @return the source reference + */ + private SourceReference buildFromMultipleSourceParameters(String[] segments, Parameter parameter) { + + boolean foundEntryMatch; + + String[] propertyNames = new String[0]; + List entries = new ArrayList<>(); + + if ( segments.length > 1 && parameter != null ) { + propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); + entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames, true ); + foundEntryMatch = ( entries.size() == propertyNames.length ); + } + else { + // its only a parameter, no property + foundEntryMatch = true; + } + + if ( !foundEntryMatch ) { + reportErrorOnNoMatch( parameter, propertyNames, entries ); + } + + return new SourceReference( parameter, entries, foundEntryMatch ); + } + + /** + * When there are more than one source parameters, the first segment name of the property + * needs to match the parameter name to avoid ambiguity + * + * consider: {@code Target map( Source1 source1, Source2 source2 )} + * entries in a @Mapping#source need to be "source1.propx" or "source2.propy.propz" to be valid + * + * @param segments the segments of @Mapping#source + * @return parameter that matches with first segment of @Mapping#source + */ + private Parameter fetchMatchingParameterFromFirstSegment(String[] segments ) { + Parameter parameter = null; + if ( segments.length > 0 ) { + String parameterName = segments[0]; + parameter = getSourceParameterFromMethodOrTemplate( parameterName ); + if ( parameter == null ) { + reportMappingError( + Message.PROPERTYMAPPING_INVALID_PARAMETER_NAME, + parameterName, + Strings.join( method.getSourceParameters(), ", ", Parameter::getName ) + ); + } + } + return parameter; + } + + private Parameter getSourceParameterFromMethodOrTemplate(String parameterName ) { + + Parameter result = null; + if ( isForwarded ) { + Parameter parameter = Parameter.getSourceParameter( templateMethod.getParameters(), parameterName ); + if ( parameter != null ) { + + // When forward inheriting we should find the matching source parameter by type + // If there are multiple parameters of the same type + // then we fallback to match the parameter name to the current method source parameters + for ( Parameter sourceParameter : method.getSourceParameters() ) { + if ( sourceParameter.getType().isAssignableTo( parameter.getType() ) ) { + if ( result == null ) { + result = sourceParameter; + } + else { + // When we reach here it means that we found a second source parameter + // that has the same type, then fallback to the matching source parameter + // in the current method + result = Parameter.getSourceParameter( method.getParameters(), parameterName ); + break; + } + } + } + } + } + else { + result = Parameter.getSourceParameter( method.getParameters(), parameterName ); + } + return result; + } + + private void reportErrorOnNoMatch( Parameter parameter, String[] propertyNames, List entries) { + if ( parameter != null ) { + reportMappingError( Message.PROPERTYMAPPING_NO_PROPERTY_IN_PARAMETER, parameter.getName(), + Strings.join( Arrays.asList( propertyNames ), "." ) + ); + } + else { + int notFoundPropertyIndex = 0; + Type sourceType = method.getParameters().get( 0 ).getType(); + if ( !entries.isEmpty() ) { + notFoundPropertyIndex = entries.size(); + sourceType = last( entries ).getType(); + } + + Set readProperties = sourceType.getPropertyReadAccessors().keySet(); + + if ( !readProperties.isEmpty() ) { + String mostSimilarWord = Strings.getMostSimilarWord( + propertyNames[notFoundPropertyIndex], + readProperties + ); + + List elements = new ArrayList<>( + Arrays.asList( propertyNames ).subList( 0, notFoundPropertyIndex ) + ); + elements.add( mostSimilarWord ); + reportMappingError( + Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME, sourceName, Strings.join( elements, "." ) + ); + } + else { + reportMappingError( + Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME_SOURCE_HAS_NO_PROPERTIES, + sourceName, + sourceType.describe() + ); + } + } + } + + private List matchWithSourceAccessorTypes(Type type, String[] entryNames, + boolean allowedMapToBean) { + List sourceEntries = new ArrayList<>(); + Type newType = type; + for ( int i = 0; i < entryNames.length; i++ ) { + boolean matchFound = false; + Type noBoundsType = newType.withoutBounds(); + if ( noBoundsType.isOptionalType() ) { + noBoundsType = noBoundsType.getOptionalBaseType(); + } + ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i], i > 0 || allowedMapToBean ); + if ( readAccessor != null ) { + PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); + newType = typeFactory.getReturnType( + (DeclaredType) noBoundsType.getTypeMirror(), + readAccessor + ); + sourceEntries.add( forSourceReference( + Arrays.copyOf( entryNames, i + 1 ), + readAccessor, + presenceChecker, + newType + ) ); + matchFound = true; + } + if ( !matchFound ) { + break; + } + } + return sourceEntries; + } + + private void reportMappingError(Message msg, Object... objects) { + messager.printMessage( method.getExecutable(), annotationMirror, sourceAnnotationValue, msg, objects ); + } + } + + /** + * Builds a {@link SourceReference} from a property. + */ + public static class BuilderFromProperty { + + private String name; + private ReadAccessor readAccessor; + private PresenceCheckAccessor presenceChecker; + private Type type; + private Parameter sourceParameter; + + public BuilderFromProperty name(String name) { + this.name = name; + return this; + } + + public BuilderFromProperty readAccessor(ReadAccessor readAccessor) { + this.readAccessor = readAccessor; + return this; + } + + public BuilderFromProperty presenceChecker(PresenceCheckAccessor presenceChecker) { + this.presenceChecker = presenceChecker; + return this; + } + + public BuilderFromProperty type(Type type) { + this.type = type; + return this; + } + + public BuilderFromProperty sourceParameter(Parameter sourceParameter) { + this.sourceParameter = sourceParameter; + return this; + } + + public SourceReference build() { + List sourcePropertyEntries = new ArrayList<>(); + if ( readAccessor != null ) { + sourcePropertyEntries.add( forSourceReference( + new String[] { name }, + readAccessor, + presenceChecker, + type + ) ); + } + return new SourceReference( sourceParameter, sourcePropertyEntries, true ); + } + } + + /** + * Builds a {@link SourceReference} from a property. + */ + public static class BuilderFromSourceReference { + + private Parameter sourceParameter; + private SourceReference sourceReference; + + public BuilderFromSourceReference sourceReference(SourceReference sourceReference) { + this.sourceReference = sourceReference; + return this; + } + + public BuilderFromSourceReference sourceParameter(Parameter sourceParameter) { + this.sourceParameter = sourceParameter; + return this; + } + + public SourceReference build() { + return new SourceReference( sourceParameter, sourceReference.getPropertyEntries(), true ); + } + } + + private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { + super( sourceParameter, sourcePropertyEntries, isValid ); + } + + public SourceReference pop() { + if ( getPropertyEntries().size() > 1 ) { + List newPropertyEntries = + new ArrayList<>( getPropertyEntries().subList( 1, getPropertyEntries().size() ) ); + return new SourceReference( getParameter(), newPropertyEntries, isValid() ); + } + else { + return null; + } + } + + public List push(TypeFactory typeFactory, FormattingMessager messager, Method method ) { + List result = new ArrayList<>(); + PropertyEntry deepestProperty = getDeepestProperty(); + if ( deepestProperty != null ) { + Type type = deepestProperty.getType(); + Map newDeepestReadAccessors = type.getPropertyReadAccessors(); + String parameterName = getParameter().getName(); + String deepestPropertyFullName = deepestProperty.getFullName(); + for ( Map.Entry newDeepestReadAccessorEntry : newDeepestReadAccessors.entrySet() ) { + // Always include the parameter name in the new full name. + // Otherwise multi source parameters might be reported incorrectly + String newFullName = + parameterName + "." + deepestPropertyFullName + "." + newDeepestReadAccessorEntry.getKey(); + SourceReference sourceReference = new BuilderFromMapping() + .sourceName( newFullName ) + .method( method ) + .messager( messager ) + .typeFactory( typeFactory ) + .build(); + result.add( sourceReference ); + } + } + return result; + } + + public SourceReference withParameter(Parameter parameter) { + if ( parameter == null || getParameter() == parameter ) { + return this; + } + return new SourceReference( parameter, getPropertyEntries(), isValid() ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java new file mode 100644 index 0000000000..961e519561 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/TargetReference.java @@ -0,0 +1,282 @@ +/* + * 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.model.beanmapping; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; + +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.MappingOptions; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * This class describes the target side of a property mapping. + *

    + * It contains the target parameter, and all individual (nested) property entries. So consider the following mapping + * method: + * + *

    + * @Mapping(target = "propC", source = "in.propA.propB")
    + * TypeB mappingMethod(TypeA in);
    + * 
    + * + * Then: + *
      + *
    • {@code parameter} will describe {@code in}
    • + *
    • {@code propertyEntries[0]} will describe {@code propA}
    • + *
    • {@code propertyEntries[1]} will describe {@code propB}
    • + *
    + * + * @author Sjaak Derksen + */ +public class TargetReference { + + private final List pathProperties; + private final Parameter parameter; + private final List propertyEntries; + + public TargetReference(Parameter parameter, List propertyEntries) { + this( parameter, propertyEntries, Collections.emptyList() ); + } + + public TargetReference(Parameter parameter, List propertyEntries, List pathProperties) { + this.pathProperties = pathProperties; + this.parameter = parameter; + this.propertyEntries = propertyEntries; + } + + public List getPathProperties() { + return pathProperties; + } + + public List getPropertyEntries() { + return propertyEntries; + } + + public List getElementNames() { + List elementNames = new ArrayList<>(); + if ( parameter != null ) { + // only relevant for source properties + elementNames.add( parameter.getName() ); + } + elementNames.addAll( propertyEntries ); + return elementNames; + } + + /** + * @return the property name on the shallowest nesting level + */ + public String getShallowestPropertyName() { + if ( propertyEntries.isEmpty() ) { + return null; + } + return first( propertyEntries ); + } + + public boolean isNested() { + return propertyEntries.size() > 1; + } + + @Override + public String toString() { + + String result = ""; + if ( propertyEntries.isEmpty() ) { + if ( parameter != null ) { + result = String.format( "parameter \"%s %s\"", parameter.getType(), parameter.getName() ); + } + } + else if ( propertyEntries.size() == 1 ) { + String propertyEntry = propertyEntries.get( 0 ); + result = String.format( "property \"%s\"", propertyEntry ); + } + else { + result = String.format( + "property \"%s\"", + Strings.join( getElementNames(), "." ) + ); + } + return result; + } + + /** + * Builds a {@link TargetReference} from an {@code @Mappping}. + */ + public static class Builder { + + private Method method; + private FormattingMessager messager; + private TypeFactory typeFactory; + private Set targetProperties; + private Type targetType; + + // mapping parameters + private String targetName = null; + private MappingOptions mapping; + private AnnotationMirror annotationMirror = null; + private AnnotationValue targetAnnotationValue = null; + private AnnotationValue sourceAnnotationValue = null; + + public Builder messager(FormattingMessager messager) { + this.messager = messager; + return this; + } + + public Builder mapping(MappingOptions mapping) { + this.mapping = mapping; + this.targetName = mapping.getTargetName(); + this.annotationMirror = mapping.getMirror(); + this.targetAnnotationValue = mapping.getTargetAnnotationValue(); + this.sourceAnnotationValue = mapping.getSourceAnnotationValue(); + return this; + } + + public Builder typeFactory(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + return this; + } + + public Builder method(Method method) { + this.method = method; + return this; + } + + public Builder targetProperties(Set targetProperties) { + this.targetProperties = targetProperties; + return this; + } + + public Builder targetType(Type targetType) { + this.targetType = targetType; + return this; + } + + public TargetReference build() { + + Objects.requireNonNull( method ); + Objects.requireNonNull( typeFactory ); + Objects.requireNonNull( messager ); + Objects.requireNonNull( targetType ); + + if ( targetName == null ) { + return null; + } + + String targetNameTrimmed = targetName.trim(); + if ( !targetName.equals( targetNameTrimmed ) ) { + messager.printMessage( + method.getExecutable(), + annotationMirror, + targetAnnotationValue, + Message.PROPERTYMAPPING_WHITESPACE_TRIMMED, + targetName, + targetNameTrimmed + ); + } + String[] segments = targetNameTrimmed.split( "\\." ); + Parameter parameter = method.getMappingTargetParameter(); + + // there can be 4 situations + // 1. Return type + // 2. An inverse target reference where the source parameter name is used in the original mapping + // 3. @MappingTarget, with + // 4. or without parameter name. + String[] targetPropertyNames = segments; + if ( segments.length > 1 ) { + String firstTargetProperty = targetPropertyNames[0]; + // If the first target property is not within the defined target properties, then check if it matches + // the source or target parameter + if ( !targetProperties.contains( firstTargetProperty ) ) { + if ( matchesSourceOrTargetParameter( firstTargetProperty, parameter ) ) { + targetPropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); + } + } + } + + + List entries = new ArrayList<>( Arrays.asList( targetPropertyNames ) ); + + return new TargetReference( parameter, entries ); + } + + /** + * Validates that the {@code segment} is the same as the {@code targetParameter} or the {@code + * inverseSourceParameter} names + * + * @param segment that needs to be checked + * @param targetParameter the target parameter if it exists + * + * @return {@code true} if the segment matches the name of the {@code targetParameter} or the name of the + * {@code inverseSourceParameter} when this is a inverse {@link TargetReference} is being built, {@code + * false} otherwise + */ + private boolean matchesSourceOrTargetParameter(String segment, Parameter targetParameter) { + boolean matchesTargetParameter = targetParameter != null && targetParameter.getName().equals( segment ); + return matchesTargetParameter || matchesSourceOnInverseSourceParameter( segment ); + } + + /** + * Needed when we are building from inverse mapping. It is needed, so we can remove the first level if it is + * needed. + * E.g. If we have a mapping like: + * + * {@literal @}Mapping( target = "letterSignature", source = "dto.signature" ) + * + * When it is inversed it will look like: + * + * {@literal @}Mapping( target = "dto.signature", source = "letterSignature" ) + * + * The {@code dto} needs to be considered as a possibility for a target name only if a Target Reference for + * a inverse is created. + * + * @param segment that needs to be checked* + * + * @return on match when inverse and segment matches the one and only source parameter + */ + private boolean matchesSourceOnInverseSourceParameter(String segment) { + + boolean result = false; + MappingOptions.InheritContext inheritContext = mapping.getInheritContext(); + if ( inheritContext != null && inheritContext.isReversed() ) { + + Method templateMethod = inheritContext.getTemplateMethod(); + // there is only source parameter by definition when applying @InheritInverseConfiguration + Parameter inverseSourceParameter = first( templateMethod.getSourceParameters() ); + result = inverseSourceParameter.getName().equals( segment ); + } + return result; + } + } + + public TargetReference pop() { + if ( getPropertyEntries().size() > 1 ) { + List newPathProperties = new ArrayList<>( this.pathProperties ); + newPathProperties.add( getPropertyEntries().get( 0 ) ); + return new TargetReference( + null, + getPropertyEntries().subList( 1, getPropertyEntries().size() ), + newPathProperties + ); + } + else { + return null; + } + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/package-info.java new file mode 100644 index 0000000000..e616342c6f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/package-info.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 + */ +/** + *

    + * helper classes used in {@link org.mapstruct.ap.internal.model.BeanMappingMethod} + *

    + */package org.mapstruct.ap.internal.model.beanmapping; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java index dead371bc7..1988e4f90b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Accessibility.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.common; @@ -27,11 +14,11 @@ * @author Andreas Gudian */ public enum Accessibility { - PRIVATE( "private" ), DEFAULT( "" ), PROTECTED( "protected" ), PUBLIC( "public" ); + PRIVATE( "private" ), PACKAGE_PRIVATE( "" ), PROTECTED( "protected" ), PUBLIC( "public" ); private final String keyword; - private Accessibility(String keyword) { + Accessibility(String keyword) { this.keyword = keyword; } @@ -50,6 +37,6 @@ else if ( modifiers.contains( Modifier.PRIVATE ) ) { return PRIVATE; } - return DEFAULT; + return PACKAGE_PRIVATE; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java new file mode 100644 index 0000000000..7ed0140d81 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java @@ -0,0 +1,158 @@ +/* + * 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.model.common; + +import java.util.List; +import java.util.Set; + +/** + * Assignment represents all kind of manners a source can be assigned to a target. + * + * @author Sjaak Derksen + */ +public interface Assignment { + + enum AssignmentType { + /** assignment is direct */ + DIRECT( true, false ), + /** assignment is type converted */ + TYPE_CONVERTED( false, true ), + /** assignment is mapped (builtin/custom) */ + MAPPED( false, false ), + /** 2 mapping methods (builtin/custom) are applied to get the target */ + MAPPED_TWICE( false, false ), + /** assignment is first mapped (builtin/custom), then the result is type converted */ + MAPPED_TYPE_CONVERTED( false, true ), + /** assignment is first type converted, and then mapped (builtin/custom) */ + TYPE_CONVERTED_MAPPED( false, true ); + + private final boolean direct; + private final boolean converted; + + AssignmentType( boolean isDirect, boolean isConverted ) { + this.direct = isDirect; + this.converted = isConverted; + } + + public boolean isDirect() { + return direct; + } + + public boolean isConverted() { + return converted; + } + + } + + /** + * returns all types required as import by the assignment statement. + * + * @return imported types + */ + Set getImportTypes(); + + /** + * returns all types exception types thrown by this assignment. + * + * @return exceptions thrown + */ + List getThrownTypes(); + + /** + * An assignment in itself can wrap another assignment. E.g.: + *
      + *
    • a MethodReference can wrap a TypeConversion, another MethodReference and ultimately a Simple
    • + *
    • a TypeConversion can wrap a MethodReference, and ultimately a Simple
    • + *
    + * + * @param assignment the assignment to set + */ + void setAssignment(Assignment assignment); + + /** + * the source reference being a source-getter, a constant, nested method call, etc. + * + * @return source reference + */ + String getSourceReference(); + + /** + * + * @return true when the source reference is the source parameter (and not a property of the source parameter type) + */ + boolean isSourceReferenceParameter(); + + /** + * the source presence checker reference + * + * @return source reference + */ + PresenceCheck getSourcePresenceCheckerReference(); + + /** + * the source type used in the matching process + * + * @return source type (can be null) + */ + Type getSourceType(); + + /** + * Creates an unique safe (local) variable name. + * + * @param desiredName the desired name + * + * @return the desired name, made unique in the scope of the bean mapping. + */ + String createUniqueVarName(String desiredName ); + + /** + * See {@link #setSourceLocalVarName(java.lang.String) } + * + * @return local variable name (can be null if not set) + */ + String getSourceLocalVarName(); + + /** + * Returns the source parameter name, to which this assignment applies. Note: the source parameter itself might + * be mapped by this assignment, or one of its properties + * + * @return the source parameter name + */ + String getSourceParameterName(); + + /** + * Replaces the sourceReference at the call site in the assignment in the template with this sourceLocalVarName. + * The sourceLocalVarName can subsequently be used for e.g. null checking. + * + * @param sourceLocalVarName source local variable name + */ + void setSourceLocalVarName(String sourceLocalVarName); + + /** + * See {@link #getSourceLoopVarName()} (java.lang.String) } + * + * @return loop variable (can be null if not set) + */ + String getSourceLoopVarName(); + + /** + * Replaces the sourceLocalVar or sourceReference at the call site in the assignment in the template with this + * sourceLoopVarName. + * The sourceLocalVar can subsequently be used for e.g. null checking. + * + * @param sourceLoopVarName loop variable + */ + void setSourceLoopVarName(String sourceLoopVarName); + + /** + * Returns whether the type of assignment + * + * @return {@link AssignmentType} + */ + AssignmentType getType(); + + boolean isCallingUpdateMethod(); +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java new file mode 100644 index 0000000000..2b9c6d626b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/BuilderType.java @@ -0,0 +1,123 @@ +/* + * 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.model.common; + +import java.util.Collection; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.spi.BuilderInfo; + +/** + * Represents the information about a builder. + * How it can be constructed, the type it is building etc. + * + * @author Filip Hrisafov + */ +public class BuilderType { + + private final Type builder; + private final Type owningType; + private final Type buildingType; + private final ExecutableElement builderCreationMethod; + private final Collection buildMethods; + + private BuilderType( + Type builder, + Type owningType, + Type buildingType, + ExecutableElement builderCreationMethod, + Collection buildMethods + ) { + this.builder = builder; + this.owningType = owningType; + this.buildingType = buildingType; + this.builderCreationMethod = builderCreationMethod; + this.buildMethods = buildMethods; + } + + /** + * The type of the builder itself. + * + * @return the type for the builder + */ + public Type getBuilder() { + return builder; + } + + /** + * The owning type of the builder, this can be the builder itself, the type that is build by the builder or some + * other type. + * + * @return the owning type + */ + public Type getOwningType() { + return owningType; + } + + /** + * The type that is being built by the builder. + * + * @return the type that is being built + */ + public Type getBuildingType() { + return buildingType; + } + + /** + * The creation method for the builder. + * + * @return the creation method for the builder + */ + public ExecutableElement getBuilderCreationMethod() { + return builderCreationMethod; + } + + /** + * The build methods that can be invoked to create the type being built. + * @return the build methods that can be invoked to create the type being built + */ + public Collection getBuildMethods() { + return buildMethods; + } + + public static BuilderType create(BuilderInfo builderInfo, Type typeToBuild, TypeFactory typeFactory, + TypeUtils typeUtils) { + if ( builderInfo == null ) { + return null; + } + + Type builder = typeFactory.getType( builderInfo.getBuilderCreationMethod().getReturnType() ); + ExecutableElement builderCreationMethod = builderInfo.getBuilderCreationMethod(); + Type owner; + TypeMirror builderCreationOwner = builderCreationMethod.getEnclosingElement().asType(); + if ( typeUtils.isSameType( builderCreationOwner, typeToBuild.getTypeMirror() ) ) { + owner = typeToBuild; + } + else if ( typeUtils.isSameType( builder.getTypeMirror(), builderCreationOwner ) ) { + owner = builder; + } + else { + owner = typeFactory.getType( builderCreationOwner ); + } + + // When the builderCreationMethod is constructor, its return type is Void. In this case the + // builder type should be the owner type. + if ( builderInfo.getBuilderCreationMethod().getKind() == ElementKind.CONSTRUCTOR ) { + builder = owner; + } + + return new BuilderType( + builder, + owner, + typeToBuild, + builderCreationMethod, + builderInfo.getBuildMethods() + ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConstructorFragment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConstructorFragment.java new file mode 100644 index 0000000000..89d2928999 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConstructorFragment.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.ap.internal.model.common; + +/** + * ConstructorFragments are 'code snippets' added to the constructor to initialize fields used by + * BuiltInMethod/HelperMethod + */ +public interface ConstructorFragment { +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java index b01bfad2fd..d3a29e30a4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ConversionContext.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.common; @@ -34,6 +21,13 @@ public interface ConversionContext { */ Type getTargetType(); + /** + * Returns the source type of this conversion. + * + * @return The source type of this conversion. + */ + Type getSourceType(); + /** * Returns the date format if this conversion or built-in method is from String to a date type (e.g. {@link Date}) * or vice versa. @@ -43,6 +37,10 @@ public interface ConversionContext { */ String getDateFormat(); + String getNumberFormat(); + + String getLocale(); + TypeFactory getTypeFactory(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidationResult.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidationResult.java index c0181be1f9..a005b17a6d 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidationResult.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidationResult.java @@ -1,23 +1,14 @@ -/** - * Copyright 2012-2015 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.model.common; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; + import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; @@ -47,8 +38,17 @@ public boolean isValid() { return isValid; } - public void printErrorMessage(FormattingMessager messager) { - messager.printMessage( validationInfo, validationInfoArgs ); + /** + * Print the error with the most specific information possible. + * + * @param messager the messager to print the error message to + * @param element the element that had the error + * @param annotation the positionHint of the annotation that had an error + * @param value the value of the annotation that had an error + */ + public void printErrorMessage(FormattingMessager messager, Element element, AnnotationMirror annotation, + AnnotationValue value) { + messager.printMessage( element, annotation, value, validationInfo, validationInfoArgs ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidator.java index 503548f164..a502601fcb 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.common; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java index 0374945938..4d54f3c3e9 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactory.java @@ -1,30 +1,21 @@ -/** - * Copyright 2012-2015 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.model.common; -import org.mapstruct.ap.internal.util.JavaTimeConstants; -import org.mapstruct.ap.internal.util.JodaTimeConstants; -import org.mapstruct.ap.internal.util.Message; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZonedDateTime; + +import org.mapstruct.ap.internal.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.XmlConstants; /** * Factory for {@link DateFormatValidator}.

    Based on the types of source / target type a specific {@link @@ -42,7 +33,6 @@ final class DateFormatValidatorFactory { private static final String ORG_JODA_TIME_FORMAT_DATE_TIME_FORMAT = "org.joda.time.format.DateTimeFormat"; private static final String FOR_PATTERN = "forPattern"; - private static final String JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR = "javax.xml.datatype.XMLGregorianCalendar"; private DateFormatValidatorFactory() { } @@ -71,13 +61,8 @@ else if ( isJodaDateTimeSupposed( sourceType, targetType ) ) { dateFormatValidator = new JodaTimeDateFormatValidator(); } else { - dateFormatValidator = new DateFormatValidator() { - @Override - public DateFormatValidationResult validate(String dateFormat) { - return new DateFormatValidationResult( true, Message.GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK, - sourceType, targetType ); - } - }; + dateFormatValidator = dateFormat -> new DateFormatValidationResult( + true, Message.GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK, sourceType, targetType ); } return dateFormatValidator; @@ -85,7 +70,7 @@ public DateFormatValidationResult validate(String dateFormat) { private static boolean isXmlGregorianCalendarSupposedToBeMapped(Type sourceType, Type targetType) { return typesEqualsOneOf( - sourceType, targetType, JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR ); + sourceType, targetType, XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ); } private static boolean isJodaDateTimeSupposed(Type sourceType, Type targetType) { @@ -102,10 +87,11 @@ private static boolean isJava8DateTimeSupposed(Type sourceType, Type targetType) return typesEqualsOneOf( sourceType, targetType, - JavaTimeConstants.LOCAL_DATE_FQN, - JavaTimeConstants.LOCAL_TIME_FQN, - JavaTimeConstants.LOCAL_DATE_TIME_FQN, - JavaTimeConstants.ZONED_DATE_TIME_FQN ); + LocalDate.class.getCanonicalName(), + LocalTime.class.getCanonicalName(), + LocalDateTime.class.getCanonicalName(), + ZonedDateTime.class.getCanonicalName() + ); } private static boolean isJavaUtilDateSupposed(Type sourceType, Type targetType) { @@ -154,6 +140,9 @@ public DateFormatValidationResult validate(String dateFormat) { catch ( InvocationTargetException e ) { return invalidDateFormat( dateFormat, e.getCause() ); } + catch ( ClassNotFoundException e ) { + return noJodaOnClassPath(); + } catch ( Exception e ) { return invalidDateFormat( dateFormat, e ); } @@ -185,4 +174,8 @@ private static DateFormatValidationResult validDateFormat(String dateFormat) { private static DateFormatValidationResult invalidDateFormat(String dateFormat, Throwable e) { return new DateFormatValidationResult( false, Message.GENERAL_INVALID_DATE, dateFormat, e.getMessage() ); } + + private static DateFormatValidationResult noJodaOnClassPath() { + return new DateFormatValidationResult( false, Message.GENERAL_JODA_NOT_ON_CLASSPATH ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java index 416063a51e..4e5eed47a8 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/DefaultConversionContext.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.common; @@ -31,16 +18,22 @@ public class DefaultConversionContext implements ConversionContext { private final FormattingMessager messager; private final Type sourceType; private final Type targetType; + private final FormattingParameters formattingParameters; private final String dateFormat; + private final String numberFormat; + private final String locale; private final TypeFactory typeFactory; public DefaultConversionContext(TypeFactory typeFactory, FormattingMessager messager, Type sourceType, - Type targetType, String dateFormat) { + Type targetType, FormattingParameters formattingParameters) { this.typeFactory = typeFactory; this.messager = messager; this.sourceType = sourceType; this.targetType = targetType; - this.dateFormat = dateFormat; + this.formattingParameters = formattingParameters; + this.dateFormat = this.formattingParameters.getDate(); + this.numberFormat = this.formattingParameters.getNumber(); + this.locale = this.formattingParameters.getLocale(); validateDateFormat(); } @@ -53,7 +46,12 @@ private void validateDateFormat() { DateFormatValidationResult validationResult = dateFormatValidator.validate( dateFormat ); if ( !validationResult.isValid() ) { - validationResult.printErrorMessage( messager ); + validationResult.printErrorMessage( + messager, + formattingParameters.getElement(), + formattingParameters.getMirror(), + formattingParameters.getDateAnnotationValue() + ); } } } @@ -63,6 +61,21 @@ public Type getTargetType() { return targetType; } + @Override + public Type getSourceType() { + return sourceType; + } + + @Override + public String getNumberFormat() { + return numberFormat; + } + + @Override + public String getLocale() { + return locale != null ? locale.toString() : null; + } + @Override public String getDateFormat() { return dateFormat; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FieldReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FieldReference.java new file mode 100644 index 0000000000..bf1f5be445 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FieldReference.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.ap.internal.model.common; + +import java.util.Map; + +/** + * reference used by BuiltInMethod/HelperMethod to create an additional field in the mapper. + */ +public interface FieldReference { + + /** + * + * @return variable name of the field + */ + String getVariableName(); + + /** + * + * @return type of the field + */ + Type getType(); + + /** + * @return additional template parameters + */ + default Map getTemplateParameter() { + return null; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FinalField.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FinalField.java new file mode 100644 index 0000000000..c3e1ef1f39 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FinalField.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.ap.internal.model.common; + +/** + * A mapper instance field, initialized as null + * + * @author Sjaak Derksen + */ +public class FinalField implements FieldReference { + + private final Type type; + private String variableName; + + public FinalField(Type type, String variableName) { + this.type = type; + this.variableName = variableName; + } + + @Override + public String getVariableName() { + return variableName; + } + + @Override + public Type getType() { + return type; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.java new file mode 100644 index 0000000000..7ef818630a --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FormattingParameters.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.ap.internal.model.common; + +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; + +/** + * Represent information about the configured formatting. + * + * @author Sjaak Derksen + */ +public class FormattingParameters { + + public static final FormattingParameters EMPTY = new FormattingParameters( null, null, null, null, null, null ); + + private final String date; + private final String number; + private final AnnotationMirror mirror; + private final AnnotationValue dateAnnotationValue; + private final Element element; + private final String locale; + + public FormattingParameters(String date, String number, AnnotationMirror mirror, + AnnotationValue dateAnnotationValue, Element element, String locale) { + this.date = date; + this.number = number; + this.mirror = mirror; + this.dateAnnotationValue = dateAnnotationValue; + this.element = element; + this.locale = locale; + } + + public String getDate() { + return date; + } + + public String getNumber() { + return number; + } + + public AnnotationMirror getMirror() { + return mirror; + } + + public AnnotationValue getDateAnnotationValue() { + return dateAnnotationValue; + } + + public Element getElement() { + return element; + } + + public String getLocale() { + return locale; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java new file mode 100644 index 0000000000..af8c7ecfb3 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ImplementationType.java @@ -0,0 +1,89 @@ +/* + * 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.model.common; + +/** + * This is a wrapper class for the Implementation types that are used within MapStruct. It contains all the + * information needed for an Iterable creation + * + * @author Filip Hrisafov + */ +public class ImplementationType { + + private final Type type; + private final boolean initialCapacityConstructor; + private final boolean loadFactorAdjustment; + private final String factoryMethodName; + + private ImplementationType( + Type type, + boolean initialCapacityConstructor, + boolean loadFactorAdjustment, + String factoryMethodName + ) { + this.type = type; + this.initialCapacityConstructor = initialCapacityConstructor; + this.loadFactorAdjustment = loadFactorAdjustment; + this.factoryMethodName = factoryMethodName; + } + + public static ImplementationType withDefaultConstructor(Type type) { + return new ImplementationType( type, false, false, null ); + } + + public static ImplementationType withInitialCapacity(Type type) { + return new ImplementationType( type, true, false, null ); + } + + public static ImplementationType withLoadFactorAdjustment(Type type) { + return new ImplementationType( type, true, true, null ); + } + + public static ImplementationType withFactoryMethod(Type type, String factoryMethodName) { + return new ImplementationType( type, true, false, factoryMethodName ); + } + + /** + * Creates new {@link ImplementationType} that has the same {@link #initialCapacityConstructor} and + * {@link #loadFactorAdjustment}, but a different underlying {@link Type} + * + * @param type to be replaced + * + * @return a new implementation type with the given {@code type} + */ + public ImplementationType createNew(Type type) { + return new ImplementationType( type, initialCapacityConstructor, loadFactorAdjustment, factoryMethodName ); + } + + /** + * @return the underlying {@link Type} + */ + public Type getType() { + return type; + } + + /** + * @return {@code true} if the underlying type has a constructor for {@code int} {@code initialCapacity}, {@code + * false} otherwise + */ + public boolean hasInitialCapacityConstructor() { + return initialCapacityConstructor; + } + + /** + * If this method returns {@code true} then {@link #hasInitialCapacityConstructor()} also returns {@code true} + * + * @return {@code true} if the underlying type needs adjustment for the initial capacity constructor, {@code + * false} otherwise + */ + public boolean isLoadFactorAdjustment() { + return loadFactorAdjustment; + } + + public String getFactoryMethodName() { + return factoryMethodName; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ModelElement.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ModelElement.java index 20e52584fe..26d1067489 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ModelElement.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ModelElement.java @@ -1,27 +1,12 @@ -/** - * Copyright 2012-2015 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.model.common; -import java.io.Writer; import java.util.Set; -import org.mapstruct.ap.internal.writer.FreeMarkerModelElementWriter; import org.mapstruct.ap.internal.writer.FreeMarkerWritable; import org.mapstruct.ap.internal.writer.Writable; @@ -33,11 +18,6 @@ */ public abstract class ModelElement extends FreeMarkerWritable { - @Override - public void write(Context context, Writer writer) throws Exception { - new FreeMarkerModelElementWriter().write( this, context, writer ); - } - /** * Returns a set containing those {@link Type}s referenced by this model element for which an import statement needs * to be declared. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.java new file mode 100644 index 0000000000..78357affb8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.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.ap.internal.model.common; + +import java.util.Objects; +import java.util.Set; + +/** + * A {@link PresenceCheck} that negates the result of another presence check. + * + * @author Filip Hrisafov + */ +public class NegatePresenceCheck extends ModelElement implements PresenceCheck { + + private final PresenceCheck presenceCheck; + + public NegatePresenceCheck(PresenceCheck presenceCheck) { + this.presenceCheck = presenceCheck; + } + + public PresenceCheck getPresenceCheck() { + return presenceCheck; + } + + @Override + public Set getImportTypes() { + return presenceCheck.getImportTypes(); + } + + @Override + public PresenceCheck negate() { + return presenceCheck; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + NegatePresenceCheck that = (NegatePresenceCheck) o; + return Objects.equals( presenceCheck, that.presenceCheck ); + } + + @Override + public int hashCode() { + return Objects.hash( presenceCheck ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/NewInstanceCreation.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NewInstanceCreation.java new file mode 100644 index 0000000000..a91d7242cb --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/NewInstanceCreation.java @@ -0,0 +1,63 @@ +/* + * 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.model.common; + +import java.util.Set; + +/** + * Model element representing the head of a {@code new T<>(...)} expression. It always renders the diamond + * operator for generic types, leaving any type-argument inference to the surrounding Java context. + *

    + * The caller is responsible for emitting the parenthesised argument list after the model. + *

    + * Imports contributed by this element only include the raw constructor type, not its type parameters: the + * generated source never references the parameter classes through this expression, so they are not needed + * here. If the surrounding code references a type parameter elsewhere (e.g. in a variable declaration or a + * method signature), that reference contributes its own imports through its own model element. + * + * @author Filip Hrisafov + */ +public class NewInstanceCreation extends ModelElement { + + private final Type type; + private final Type rawType; + + private NewInstanceCreation(Type type) { + this.type = type; + this.rawType = type.asRawType(); + } + + /** + * Creates a {@link NewInstanceCreation} for the given target type. If the target has an implementation + * type (e.g. {@code Collection} -> {@code ArrayList}), the implementation type is used. + * + * @param targetType the target type to be instantiated; must not be {@code null} + * @return a new model + */ + public static NewInstanceCreation forType(Type targetType) { + Type effective = targetType.getImplementationType() != null + ? targetType.getImplementationType() + : targetType; + return new NewInstanceCreation( effective ); + } + + public Type getType() { + return type; + } + + public Type getRawType() { + return rawType; + } + + public boolean isGeneric() { + return !type.getTypeParameters().isEmpty(); + } + + @Override + public Set getImportTypes() { + return rawType.getImportTypes(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index f2444d5fcd..c5091869a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -1,25 +1,22 @@ -/** - * Copyright 2012-2015 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.model.common; +import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import org.mapstruct.ap.internal.gem.ContextGem; +import org.mapstruct.ap.internal.gem.MappingTargetGem; +import org.mapstruct.ap.internal.gem.SourcePropertyNameGem; +import org.mapstruct.ap.internal.gem.TargetPropertyNameGem; +import org.mapstruct.ap.internal.gem.TargetTypeGem; import org.mapstruct.ap.internal.util.Collections; /** @@ -29,26 +26,67 @@ */ public class Parameter extends ModelElement { + private final Element element; private final String name; + private final String originalName; private final Type type; private final boolean mappingTarget; private final boolean targetType; + private final boolean mappingContext; + private final boolean sourcePropertyName; + private final boolean targetPropertyName; + + private final boolean varArgs; + + private Parameter(Element element, Type type, boolean varArgs) { + this.element = element; + this.name = element.getSimpleName().toString(); + this.originalName = name; + this.type = type; + this.mappingTarget = MappingTargetGem.instanceOn( element ) != null; + this.targetType = TargetTypeGem.instanceOn( element ) != null; + this.mappingContext = ContextGem.instanceOn( element ) != null; + this.sourcePropertyName = SourcePropertyNameGem.instanceOn( element ) != null; + this.targetPropertyName = TargetPropertyNameGem.instanceOn( element ) != null; + this.varArgs = varArgs; + } - public Parameter(String name, Type type, boolean mappingTarget, boolean targetType) { + private Parameter(String name, String originalName, Type type, boolean mappingTarget, boolean targetType, + boolean mappingContext, + boolean sourcePropertyName, boolean targetPropertyName, + boolean varArgs) { + this.element = null; this.name = name; + this.originalName = originalName; this.type = type; this.mappingTarget = mappingTarget; this.targetType = targetType; + this.mappingContext = mappingContext; + this.sourcePropertyName = sourcePropertyName; + this.targetPropertyName = targetPropertyName; + this.varArgs = varArgs; } public Parameter(String name, Type type) { - this( name, type, false, false ); + this( name, name, type ); + } + + public Parameter(String name, String originalName, Type type) { + this( name, originalName, type, false, false, false, false, false, false ); + } + + public Element getElement() { + return element; } public String getName() { return name; } + public String getOriginalName() { + return originalName; + } + public Type getType() { return type; } @@ -59,8 +97,20 @@ public boolean isMappingTarget() { @Override public String toString() { - return ( mappingTarget ? "@MappingTarget " : "" ) + ( targetType ? "@TargetType " : "" ) - + type.toString() + " " + name; + return String.format( format(), type ); + } + + public String describe() { + return String.format( format(), type.describe() ); + } + + private String format() { + return ( mappingTarget ? "@MappingTarget " : "" ) + + ( targetType ? "@TargetType " : "" ) + + ( mappingContext ? "@Context " : "" ) + + ( sourcePropertyName ? "@SourcePropertyName " : "" ) + + ( targetPropertyName ? "@TargetPropertyName " : "" ) + + "%s " + name; } @Override @@ -72,26 +122,134 @@ public boolean isTargetType() { return targetType; } + public boolean isMappingContext() { + return mappingContext; + } + + public boolean isTargetPropertyName() { + return targetPropertyName; + } + + public boolean isSourcePropertyName() { + return sourcePropertyName; + } + + public boolean isVarArgs() { + return varArgs; + } + + public boolean isSourceParameter() { + return !isMappingTarget() && + !isTargetType() && + !isMappingContext() && + !isSourcePropertyName() && + !isTargetPropertyName(); + } + + public Parameter withName(String name) { + return new Parameter( + name, + this.name, + type, + mappingTarget, + targetType, + mappingContext, + sourcePropertyName, + targetPropertyName, + varArgs + ); + } + @Override public int hashCode() { - int hash = 5; - hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0); - return hash; + int result = name != null ? name.hashCode() : 0; + result = 31 * result + ( type != null ? type.hashCode() : 0 ); + return result; } @Override - public boolean equals(Object obj) { - if ( obj == null ) { - return false; + public boolean equals(Object o) { + if ( this == o ) { + return true; } - if ( getClass() != obj.getClass() ) { + if ( o == null || getClass() != o.getClass() ) { return false; } - final Parameter other = (Parameter) obj; - if ( (this.name == null) ? (other.name != null) : !this.name.equals( other.name ) ) { + + Parameter parameter = (Parameter) o; + + if ( !Objects.equals( name, parameter.name ) ) { return false; } - return true; + return Objects.equals( type, parameter.type ); + + } + + public static Parameter forElementAndType(VariableElement element, Type parameterType, boolean isVarArgs) { + return new Parameter( + element, + parameterType, + isVarArgs + ); + } + + public static Parameter forForgedMappingTarget(Type parameterType) { + return new Parameter( + "mappingTarget", + "mappingTarget", + parameterType, + true, + false, + false, + false, + false, + false + ); + } + + /** + * @param parameters the parameters to filter + * @return the parameters from the given list that are considered 'source parameters' + */ + public static List getSourceParameters(List parameters) { + return parameters.stream().filter( Parameter::isSourceParameter ).collect( Collectors.toList() ); + } + + /** + * @param parameters the parameters to scan + * @param sourceParameterName the source parameter name to match + * @return the parameters from the given list that are considered 'source parameters' + */ + public static Parameter getSourceParameter(List parameters, String sourceParameterName) { + return parameters.stream() + .filter( Parameter::isSourceParameter ) + .filter( parameter -> parameter.getName().equals( sourceParameterName ) ) + .findAny() + .orElse( null ); + } + + /** + * @param parameters the parameters to filter + * @return the parameters from the given list that are marked as 'mapping context parameters' + */ + public static List getContextParameters(List parameters) { + return parameters.stream().filter( Parameter::isMappingContext ).collect( Collectors.toList() ); + } + + public static Parameter getMappingTargetParameter(List parameters) { + return parameters.stream().filter( Parameter::isMappingTarget ).findAny().orElse( null ); + } + + public static Parameter getTargetTypeParameter(List parameters) { + return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null ); + } + + public static Parameter getSourcePropertyNameParameter(List parameters) { + return parameters.stream().filter( Parameter::isSourcePropertyName ).findAny().orElse( null ); + } + + public static Parameter getTargetPropertyNameParameter(List parameters) { + return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java new file mode 100644 index 0000000000..c1a594c73a --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java @@ -0,0 +1,230 @@ +/* + * 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.model.common; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +/** + * Represents how one parameter of a method to be called is populated. + * + * @author Andreas Gudian + */ +public class ParameterBinding { + + private final Type type; + private final String variableName; + private final SourceRHS sourceRHS; + private final Collection bindingTypes; + + private ParameterBinding(Type parameterType, String variableName, Collection bindingTypes, + SourceRHS sourceRHS) { + this.type = parameterType; + this.variableName = variableName; + this.bindingTypes = bindingTypes; + this.sourceRHS = sourceRHS; + } + + /** + * @return the name of the variable (or parameter) that is being used as argument for the parameter being bound. + */ + public String getVariableName() { + return variableName; + } + + public boolean isSourceParameter() { + return bindingTypes.contains( BindingType.PARAMETER ); + } + + /** + * @return {@code true}, if the parameter being bound is a {@code @TargetType} parameter. + */ + public boolean isTargetType() { + return bindingTypes.contains( BindingType.TARGET_TYPE ); + } + + /** + * @return {@code true}, if the parameter being bound is a {@code @MappingTarget} parameter. + */ + public boolean isMappingTarget() { + return bindingTypes.contains( BindingType.MAPPING_TARGET ); + } + + /** + * @return {@code true}, if the parameter being bound is a {@code @MappingContext} parameter. + */ + public boolean isMappingContext() { + return bindingTypes.contains( BindingType.CONTEXT ); + } + + public boolean isForSourceRhs() { + return bindingTypes.contains( BindingType.SOURCE_RHS ); + } + + /** + * @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter. + */ + public boolean isSourcePropertyName() { + return bindingTypes.contains( BindingType.SOURCE_PROPERTY_NAME ); + } + + /** + * @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter. + */ + public boolean isTargetPropertyName() { + return bindingTypes.contains( BindingType.TARGET_PROPERTY_NAME ); + } + + /** + * @return the type of the parameter that is bound + */ + public Type getType() { + return type; + } + + /** + * @return the sourceRHS that this parameter is bound to + */ + public SourceRHS getSourceRHS() { + return sourceRHS; + } + + public Set getImportTypes() { + if ( isTargetType() ) { + return type.getImportTypes(); + } + + if ( sourceRHS != null ) { + return sourceRHS.getImportTypes(); + } + + return Collections.emptySet(); + } + + /** + * @param parameter parameter + * @return a parameter binding reflecting the given parameter as being used as argument for a method call + */ + public static ParameterBinding fromParameter(Parameter parameter) { + EnumSet bindingTypes = EnumSet.of( BindingType.PARAMETER ); + if ( parameter.isMappingTarget() ) { + bindingTypes.add( BindingType.MAPPING_TARGET ); + } + + if ( parameter.isTargetType() ) { + bindingTypes.add( BindingType.TARGET_TYPE ); + } + + if ( parameter.isMappingContext() ) { + bindingTypes.add( BindingType.CONTEXT ); + } + + if ( parameter.isSourcePropertyName() ) { + bindingTypes.add( BindingType.SOURCE_PROPERTY_NAME ); + } + + if ( parameter.isTargetPropertyName() ) { + bindingTypes.add( BindingType.TARGET_PROPERTY_NAME ); + } + + return new ParameterBinding( + parameter.getType(), + parameter.getName(), + bindingTypes, + null + ); + } + + public static List fromParameters(List parameters) { + List result = new ArrayList<>( parameters.size() ); + for ( Parameter param : parameters ) { + result.add( fromParameter( param ) ); + } + return result; + } + + public static ParameterBinding fromTypeAndName(Type parameterType, String parameterName) { + return new ParameterBinding( + parameterType, + parameterName, + Collections.emptySet(), + null + ); + } + + /** + * @param classTypeOf the type representing {@code Class} for the target type {@code X} + * @return a parameter binding representing a target type parameter + */ + public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { + return new ParameterBinding( classTypeOf, null, Collections.singleton( BindingType.TARGET_TYPE ), null ); + } + + /** + * @return a parameter binding representing a target property name parameter + */ + public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { + return new ParameterBinding( + classTypeOf, + null, + Collections.singleton( BindingType.TARGET_PROPERTY_NAME ), + null + ); + } + + /** + * @return a parameter binding representing a source property name parameter + */ + public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) { + return new ParameterBinding( + classTypeOf, + null, + Collections.singleton( BindingType.SOURCE_PROPERTY_NAME ), + null + ); + } + + /** + * @param resultType type of the mapping target + * @return a parameter binding representing a mapping target parameter + */ + public static ParameterBinding forMappingTargetBinding(Type resultType) { + return new ParameterBinding( resultType, null, Collections.singleton( BindingType.MAPPING_TARGET ), null ); + } + + /** + * @param sourceType type of the parameter + * @return a parameter binding representing a mapping source type + */ + public static ParameterBinding forSourceTypeBinding(Type sourceType) { + return new ParameterBinding( sourceType, null, Collections.singleton( BindingType.SOURCE_TYPE ), null ); + } + + public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) { + return new ParameterBinding( + sourceRHS.getSourceType(), + null, + Collections.singleton( BindingType.SOURCE_RHS ), + sourceRHS + ); + } + + enum BindingType { + PARAMETER, + FROM_TYPE_AND_NAME, + TARGET_TYPE, + TARGET_PROPERTY_NAME, + SOURCE_PROPERTY_NAME, + MAPPING_TARGET, + CONTEXT, + SOURCE_TYPE, + SOURCE_RHS + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/PresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/PresenceCheck.java new file mode 100644 index 0000000000..ec286480d9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/PresenceCheck.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.ap.internal.model.common; + +import java.util.Set; + +/** + * Marker interface for presence checks. + * + * @author Filip Hrisafov + */ +public interface PresenceCheck { + + /** + * returns all types required as import by the presence check. + * + * @return imported types + */ + Set getImportTypes(); + + PresenceCheck negate(); + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java new file mode 100644 index 0000000000..b4d422c797 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/SourceRHS.java @@ -0,0 +1,176 @@ +/* + * 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.model.common; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import org.mapstruct.ap.internal.util.Strings; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * SourceRHS Assignment. Right Hand Side (RHS), source part of the assignment. + * + * This class contains all information on the source side of an assignment needed for common use in the mapping. + * + * @author Sjaak Derksen + */ +public class SourceRHS extends ModelElement implements Assignment { + + private final String sourceReference; + private final Type sourceType; + private String sourceLocalVarName; + private String sourceLoopVarName; + private final Set existingVariableNames; + private final String sourceErrorMessagePart; + private PresenceCheck sourcePresenceCheckerReference; + private boolean useElementAsSourceTypeForMatching = false; + private final String sourceParameterName; + + public SourceRHS(String sourceReference, Type sourceType, Set existingVariableNames, + String sourceErrorMessagePart ) { + this( sourceReference, sourceReference, null, sourceType, existingVariableNames, sourceErrorMessagePart ); + } + + public SourceRHS(String sourceParameterName, String sourceReference, PresenceCheck sourcePresenceCheckerReference, + Type sourceType, Set existingVariableNames, String sourceErrorMessagePart ) { + this.sourceReference = sourceReference; + this.sourceType = sourceType; + this.existingVariableNames = existingVariableNames; + this.sourceErrorMessagePart = sourceErrorMessagePart; + this.sourcePresenceCheckerReference = sourcePresenceCheckerReference; + this.sourceParameterName = sourceParameterName; + } + + @Override + public String getSourceReference() { + return sourceReference; + } + + @Override + public boolean isSourceReferenceParameter() { + return sourceReference.equals( sourceParameterName ); + } + + @Override + public PresenceCheck getSourcePresenceCheckerReference() { + return sourcePresenceCheckerReference; + } + + public void setSourcePresenceCheckerReference(PresenceCheck sourcePresenceCheckerReference) { + this.sourcePresenceCheckerReference = sourcePresenceCheckerReference; + } + + @Override + public Type getSourceType() { + return sourceType; + } + + @Override + public String createUniqueVarName(String desiredName) { + String result = Strings.getSafeVariableName( desiredName, existingVariableNames ); + existingVariableNames.add( result ); + return result; + } + + @Override + public String getSourceLocalVarName() { + return sourceLocalVarName; + } + + @Override + public void setSourceLocalVarName(String sourceLocalVarName) { + this.sourceLocalVarName = sourceLocalVarName; + } + + public String getSourceLoopVarName() { + return sourceLoopVarName; + } + + public void setSourceLoopVarName(String sourceLoopVarName) { + this.sourceLoopVarName = sourceLoopVarName; + } + + @Override + public Set getImportTypes() { + if ( sourcePresenceCheckerReference != null ) { + return sourcePresenceCheckerReference.getImportTypes(); + } + + return Collections.emptySet(); + } + + @Override + public List getThrownTypes() { + return Collections.emptyList(); + } + + @Override + public void setAssignment( Assignment assignment ) { + throw new UnsupportedOperationException( "Not supported." ); + } + + @Override + public AssignmentType getType() { + return AssignmentType.DIRECT; + } + + @Override + public boolean isCallingUpdateMethod() { + return false; + } + + @Override + public String toString() { + return sourceReference; + } + + public String getSourceErrorMessagePart() { + return sourceErrorMessagePart; + } + + /** + * The source type that is to be used when resolving the mapping from source to target. + * + * @return the source type to be used in the matching process. + */ + public Type getSourceTypeForMatching() { + if ( useElementAsSourceTypeForMatching ) { + if ( sourceType.isCollectionType() ) { + return first( sourceType.determineTypeArguments( Collection.class ) ); + } + else if ( sourceType.isStreamType() ) { + return first( sourceType.determineTypeArguments( Stream.class ) ); + } + else if ( sourceType.isArrayType() ) { + return sourceType.getComponentType(); + } + else if ( sourceType.isIterableType() ) { + return first( sourceType.determineTypeArguments( Iterable.class ) ); + } + } + return sourceType; + } + + /** + * For collection type, use element as source type to find a suitable mapping method. + * + * @param useElementAsSourceTypeForMatching uses the element of a collection as source type for the matching process + */ + public void setUseElementAsSourceTypeForMatching(boolean useElementAsSourceTypeForMatching) { + this.useElementAsSourceTypeForMatching = useElementAsSourceTypeForMatching; + } + + @Override + public String getSourceParameterName() { + return sourceParameterName; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 611a315c4b..0c02870352 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -1,52 +1,77 @@ -/** - * Copyright 2012-2015 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.model.common; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; import java.util.Set; - +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; +import javax.lang.model.element.NestingKind; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; +import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.SimpleTypeVisitor8; + +import kotlin.Metadata; +import kotlin.metadata.Attributes; +import kotlin.metadata.KmClass; +import kotlin.metadata.KmConstructor; +import kotlin.metadata.Modality; +import kotlin.metadata.jvm.JvmExtensionsKt; +import kotlin.metadata.jvm.JvmMethodSignature; +import kotlin.metadata.jvm.KotlinClassMetadata; +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.util.AccessorNamingUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.Executables; import org.mapstruct.ap.internal.util.Filters; +import org.mapstruct.ap.internal.util.JSpecifyConstants; +import org.mapstruct.ap.internal.util.JavaStreamConstants; +import org.mapstruct.ap.internal.util.NativeTypes; import org.mapstruct.ap.internal.util.Nouns; -import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; +import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +import org.mapstruct.ap.internal.util.kotlin.KotlinMetadata; + +import static java.util.Collections.emptyList; +import static org.mapstruct.ap.internal.util.Collections.first; /** * Represents (a reference to) the type of a bean property, parameter etc. Types are managed per generated source file. @@ -57,22 +82,49 @@ * through {@link TypeFactory}. * * @author Gunnar Morling + * @author Filip Hrisafov */ public class Type extends ModelElement implements Comparable { + private static final Method SEALED_PERMITTED_SUBCLASSES_METHOD; + private static final boolean KOTLIN_METADATA_JVM_PRESENT; + + static { + Method permittedSubclassesMethod; + try { + permittedSubclassesMethod = TypeElement.class.getMethod( "getPermittedSubclasses" ); + } + catch ( NoSuchMethodException e ) { + permittedSubclassesMethod = null; + } + SEALED_PERMITTED_SUBCLASSES_METHOD = permittedSubclassesMethod; - private final Types typeUtils; - private final Elements elementUtils; + boolean kotlinMetadataJvmPresent; + try { + Class.forName( "kotlin.metadata.jvm.KotlinClassMetadata", false, ModelElement.class.getClassLoader() ); + kotlinMetadataJvmPresent = true; + } + catch ( ClassNotFoundException e ) { + kotlinMetadataJvmPresent = false; + } + KOTLIN_METADATA_JVM_PRESENT = kotlinMetadataJvmPresent; + } + + private final TypeUtils typeUtils; + private final ElementUtils elementUtils; private final TypeFactory typeFactory; + private final AccessorNamingUtils accessorNaming; private final TypeMirror typeMirror; private final TypeElement typeElement; private final List typeParameters; - private final Type implementationType; + private final ImplementationType implementationType; private final Type componentType; + private final Type topLevelType; private final String packageName; private final String name; + private final String nameWithTopLevelTypeName; private final String qualifiedName; private final boolean isInterface; @@ -80,33 +132,58 @@ public class Type extends ModelElement implements Comparable { private final boolean isIterableType; private final boolean isCollectionType; private final boolean isMapType; - private final boolean isImported; private final boolean isVoid; + private final boolean isStream; + private final boolean isLiteral; + + private final boolean loggingVerbose; private final List enumConstants; - private Map getters = null; + private final Map toBeImportedTypes; + private final Map notToBeImportedTypes; + private Boolean isToBeImported; + + private Map readAccessors = null; + private Map presenceCheckers = null; - private List allExecutables = null; - private List setters = null; - private List adders = null; - private List alternativeTargetAccessors = null; + private List allMethods = null; + private List allFields = null; + private List recordComponents = null; + private List setters = null; + private List adders = null; + private List alternativeTargetAccessors = null; private Type boundingBase = null; + private List boundTypes = null; + + private Type boxedEquivalent = null; + + private Boolean hasAccessibleConstructor; + private Boolean isNullMarked; + private KotlinMetadata kotlinMetadata; + private boolean kotlinMetadataInitialized; + private final Filters filters; //CHECKSTYLE:OFF - public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory, + public Type(TypeUtils typeUtils, ElementUtils elementUtils, TypeFactory typeFactory, + AccessorNamingUtils accessorNaming, TypeMirror typeMirror, TypeElement typeElement, - List typeParameters, Type implementationType, Type componentType, + List typeParameters, ImplementationType implementationType, Type componentType, String packageName, String name, String qualifiedName, boolean isInterface, boolean isEnumType, boolean isIterableType, - boolean isCollectionType, boolean isMapType, boolean isImported) { + boolean isCollectionType, boolean isMapType, boolean isStreamType, + Map toBeImportedTypes, + Map notToBeImportedTypes, + Boolean isToBeImported, + boolean isLiteral, boolean loggingVerbose) { this.typeUtils = typeUtils; this.elementUtils = elementUtils; this.typeFactory = typeFactory; + this.accessorNaming = accessorNaming; this.typeMirror = typeMirror; this.typeElement = typeElement; @@ -123,11 +200,12 @@ public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory, this.isIterableType = isIterableType; this.isCollectionType = isCollectionType; this.isMapType = isMapType; - this.isImported = isImported; + this.isStream = isStreamType; this.isVoid = typeMirror.getKind() == TypeKind.VOID; + this.isLiteral = isLiteral; if ( isEnumType ) { - enumConstants = new ArrayList(); + enumConstants = new ArrayList<>(); for ( Element element : typeElement.getEnclosedElements() ) { // #162: The check for visibility shouldn't be required, but the Eclipse compiler implementation @@ -141,6 +219,26 @@ public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory, else { enumConstants = Collections.emptyList(); } + + this.isToBeImported = isToBeImported; + this.toBeImportedTypes = toBeImportedTypes; + this.notToBeImportedTypes = notToBeImportedTypes; + this.filters = new Filters( accessorNaming, typeUtils, typeMirror ); + + this.loggingVerbose = loggingVerbose; + + TypeElement typeElementForTopLevel; + if ( Boolean.TRUE.equals( isToBeImported ) ) { + // If the is to be imported is explicitly set to true then we shouldn't look for the top level type + typeElementForTopLevel = null; + } + else { + // The top level type for an array type is the top level type of the component type + typeElementForTopLevel = + this.componentType == null ? this.typeElement : this.componentType.getTypeElement(); + } + this.topLevelType = topLevelType( typeElementForTopLevel, this.typeFactory ); + this.nameWithTopLevelTypeName = nameWithTopLevelTypeName( typeElementForTopLevel, this.name ); } //CHECKSTYLE:ON @@ -160,6 +258,43 @@ public String getName() { return name; } + /** + * Returns a String that could be used in generated code to reference to this {@link Type}.
    + *

    + * The first time a name is referred-to it will be marked as to be imported. For instance + * {@code LocalDateTime} can be one of {@code java.time.LocalDateTime} and {@code org.joda.LocalDateTime}) + *

    + * If the {@code java.time} variant is referred to first, the {@code java.time.LocalDateTime} will be imported + * and the {@code org.joda} variant will be referred to with its FQN. + *

    + * If the type is nested and its top level type is to be imported + * then the name including its top level type will be returned. + * + * @return Just the name if this {@link Type} will be imported, the name up to the top level {@link Type} + * (if the top level type is important, otherwise the fully-qualified name. + */ + public String createReferenceName() { + if ( isToBeImported() ) { + // isToBeImported() returns true for arrays. + // Therefore, we need to check the top level type when creating the reference + if ( isTopLevelTypeToBeImported() ) { + return nameWithTopLevelTypeName != null ? nameWithTopLevelTypeName : name; + } + + return name; + } + + if ( shouldUseSimpleName() ) { + return name; + } + + if ( isTopLevelTypeToBeImported() && nameWithTopLevelTypeName != null ) { + return nameWithTopLevelTypeName; + } + + return qualifiedName; + } + public List getTypeParameters() { return typeParameters; } @@ -184,6 +319,61 @@ public boolean isVoid() { return isVoid; } + public boolean isAbstract() { + return typeElement != null && typeElement.getModifiers().contains( Modifier.ABSTRACT ); + } + + public boolean isString() { + return String.class.getName().equals( getFullyQualifiedName() ); + } + + /** + * Whether this type is within a JSpecify {@code @NullMarked} scope. Walks the enclosing-element + * chain (this type, outer classes, package) and returns at the first {@code @NullMarked} or + * {@code @NullUnmarked} encountered — the closest annotation wins. Module-level annotations + * are only reached when the compiler populates {@code PackageElement.getEnclosingElement()} + * with a {@link javax.lang.model.element.ModuleElement} (JPMS only). + *

    + * The result is memoized on this {@code Type} instance. {@link TypeFactory#getType} does not + * intern {@code Type} instances, so callers that invoke this repeatedly should cache the + * {@code Type} reference or the result. + * + * @return {@code true} if the closest enclosing annotation is {@code @NullMarked}; + * {@code false} if it is {@code @NullUnmarked} or if no such annotation was found + */ + public boolean isNullMarked() { + if ( isNullMarked == null ) { + isNullMarked = resolveNullMarked(); + } + return isNullMarked; + } + + private boolean resolveNullMarked() { + if ( typeElement == null ) { + return false; + } + Element current = typeElement; + while ( current != null ) { + for ( AnnotationMirror mirror : current.getAnnotationMirrors() ) { + Element annotationElement = mirror.getAnnotationType().asElement(); + if ( !( annotationElement instanceof TypeElement ) ) { + // Defensive: unresolved annotations (e.g. ErrorType during incremental + // builds) can produce a non-TypeElement. Skip instead of crashing. + continue; + } + String fqn = ( (TypeElement) annotationElement ).getQualifiedName().toString(); + if ( JSpecifyConstants.NULL_MARKED_FQN.equals( fqn ) ) { + return true; + } + if ( JSpecifyConstants.NULL_UNMARKED_FQN.equals( fqn ) ) { + return false; + } + } + current = current.getEnclosingElement(); + } + return false; + } + /** * @return this type's enum constants in case it is an enum, an empty list otherwise. */ @@ -200,6 +390,10 @@ public List getEnumConstants() { * type, {@code null} otherwise. */ public Type getImplementationType() { + return implementationType != null ? implementationType.getType() : null; + } + + public ImplementationType getImplementation() { return implementationType; } @@ -212,6 +406,16 @@ public boolean isIterableType() { return isIterableType || isArrayType(); } + /** + * Whether this type is a sub-type of{@link Iterable}, {@link java.util.stream.Stream} or an array type + * + * @return {@code true} if this type is a sub-type of{@link Iterable}, {@link java.util.stream.Stream} or + * an array type, {@code false} otherwise + */ + public boolean isIterableOrStreamType() { + return isIterableType() || isStreamType(); + } + public boolean isCollectionType() { return isCollectionType; } @@ -220,6 +424,17 @@ public boolean isMapType() { return isMapType; } + private boolean hasStringMapSignature() { + if ( isMapType() ) { + List typeParameters = getTypeParameters(); + if ( typeParameters.size() == 2 && typeParameters.get( 0 ).isString() ) { + return true; + } + } + + return false; + } + public boolean isCollectionOrMapType() { return isCollectionType || isMapType; } @@ -228,11 +443,67 @@ public boolean isArrayType() { return componentType != null; } + private boolean isType(Class type) { + return type.getName().equals( getFullyQualifiedName() ); + } + + public boolean isOptionalType() { + return isType( Optional.class ) || isType( OptionalInt.class ) || isType( OptionalDouble.class ) || + isType( OptionalLong.class ); + } + + public Type getOptionalBaseType() { + if ( isType( Optional.class ) ) { + return getTypeParameters().get( 0 ); + } + + if ( isType( OptionalInt.class ) ) { + return typeFactory.getType( int.class ); + } + + if ( isType( OptionalDouble.class ) ) { + return typeFactory.getType( double.class ); + } + + if ( isType( OptionalLong.class ) ) { + return typeFactory.getType( long.class ); + } + + throw new IllegalStateException( "getOptionalBaseType should only be called for Optional types." ); + + } + public boolean isTypeVar() { return (typeMirror.getKind() == TypeKind.TYPEVAR); } - public boolean isWildCardSuperBound() { + public boolean isIntersection() { + return typeMirror.getKind() == TypeKind.INTERSECTION; + } + + public boolean isJavaLangType() { + return packageName != null && packageName.startsWith( "java." ); + } + + public boolean isRecord() { + return typeElement.getKind().name().equals( "RECORD" ); + } + + /** + * Whether this type is a sub-type of {@link java.util.stream.Stream}. + * + * @return {@code true} it this type is a sub-type of {@link java.util.stream.Stream}, {@code false otherwise} + */ + public boolean isStreamType() { + return isStream; + } + + /** + * A wild card type can have two types of bounds (mutual exclusive): extends and super. + * + * @return true if the bound has a wild card super bound (e.g. {@code ? super Number}) + */ + public boolean hasSuperBound() { boolean result = false; if ( typeMirror.getKind() == TypeKind.WILDCARD ) { WildcardType wildcardType = (WildcardType) typeMirror; @@ -241,7 +512,12 @@ public boolean isWildCardSuperBound() { return result; } - public boolean isWildCardExtendsBound() { + /** + * A wild card type can have two types of bounds (mutual exclusive): extends and super. + * + * @return true if the bound has a wild card extends bound (e.g. {@code ? extends Number}) + */ + public boolean hasExtendsBound() { boolean result = false; if ( typeMirror.getKind() == TypeKind.WILDCARD ) { WildcardType wildcardType = (WildcardType) typeMirror; @@ -250,6 +526,39 @@ public boolean isWildCardExtendsBound() { return result; } + /** + * A type variable type can have two types of bounds (mutual exclusive): lower and upper. + * + * Note that its use is only permitted on a definition (not on the place where its used). For instance: + * {@code T map( T in)} + * + * @return true if the bound has a type variable lower bound (e.g. T super Number) + */ + public boolean hasLowerBound() { + boolean result = false; + if ( typeMirror.getKind() == TypeKind.TYPEVAR ) { + TypeVariable typeVarType = (TypeVariable) typeMirror; + result = typeVarType.getLowerBound() != null; + } + return result; + } + + /** + * A type variable type can have two types of bounds (mutual exclusive): lower and upper. + * + * Note that its use is only permitted on a definition (not on the place where its used). For instance: + * {@code> T map( T in)} + * + * @return true if the bound has a type variable upper bound (e.g. T extends Number) + */ + public boolean hasUpperBound() { + boolean result = false; + if ( typeMirror.getKind() == TypeKind.TYPEVAR ) { + TypeVariable typeVarType = (TypeVariable) typeMirror; + result = typeVarType.getUpperBound() != null; + } + return result; + } public String getFullyQualifiedName() { return qualifiedName; @@ -259,61 +568,85 @@ public String getFullyQualifiedName() { * @return The name of this type as to be used within import statements. */ public String getImportName() { - return isArrayType() ? qualifiedName.substring( 0, qualifiedName.length() - 2 ) : qualifiedName; + return isArrayType() ? trimSimpleClassName( qualifiedName ) : qualifiedName; } @Override public Set getImportTypes() { - Set result = new HashSet(); + Set result = new HashSet<>(); - result.add( this ); + if ( getTypeMirror().getKind() == TypeKind.DECLARED ) { + result.add( this ); + } if ( componentType != null ) { result.addAll( componentType.getImportTypes() ); } + if ( topLevelType != null ) { + result.addAll( topLevelType.getImportTypes() ); + } + for ( Type parameter : typeParameters ) { result.addAll( parameter.getImportTypes() ); } + if ( ( hasExtendsBound() || hasSuperBound() ) && getTypeBound() != null ) { + result.addAll( getTypeBound().getImportTypes() ); + } + return result; } - /** - * Whether this type is imported by means of an import statement in the currently generated source file (meaning it - * can be referenced in the generated source using its simple name) or not (meaning it has to be referenced using - * the fully-qualified name). - * - * @return {@code true} if the type is imported, {@code false} otherwise. - */ - public boolean isImported() { - return isImported; + protected boolean isTopLevelTypeToBeImported() { + return topLevelType != null && topLevelType.isToBeImported(); } /** - * @param annotationTypeName the fully qualified name of the annotation type + * Whether this type is to be imported by means of an import statement in the currently generated source file + * (it can be referenced in the generated source using its simple name) or not (referenced using the FQN). * - * @return true, if the type is annotated with an annotation of the specified type (super-types are not inspected) + * @return {@code true} if the type is imported, {@code false} otherwise. */ - public boolean isAnnotatedWith(String annotationTypeName) { - List annotationMirrors = typeElement.getAnnotationMirrors(); + public boolean isToBeImported() { + if ( isToBeImported == null ) { + String trimmedName = trimSimpleClassName( name ); + if ( notToBeImportedTypes.containsKey( trimmedName ) ) { + isToBeImported = false; + return isToBeImported; + } + String trimmedQualifiedName = trimSimpleClassName( qualifiedName ); + String importedType = toBeImportedTypes.get( trimmedName ); - for ( AnnotationMirror mirror : annotationMirrors ) { - Name mirrorAnnotationName = ( (TypeElement) mirror.getAnnotationType().asElement() ).getQualifiedName(); - if ( mirrorAnnotationName.contentEquals( annotationTypeName ) ) { - return true; + isToBeImported = false; + if ( importedType != null ) { + if ( importedType.equals( trimmedQualifiedName ) ) { + isToBeImported = true; + } + } + else if ( typeElement == null || !typeElement.getNestingKind().isNested() ) { + toBeImportedTypes.put( trimmedName, trimmedQualifiedName ); + isToBeImported = true; } } + return isToBeImported; + } - return false; + private boolean shouldUseSimpleName() { + // Using trimSimpleClassName since the same is used in the isToBeImported() + // to check whether notToBeImportedTypes contains it + String trimmedName = trimSimpleClassName( name ); + String fqn = notToBeImportedTypes.get( trimmedName ); + return trimSimpleClassName( this.qualifiedName ).equals( fqn ); } public Type erasure() { return new Type( typeUtils, elementUtils, - typeFactory, - SpecificCompilerWorkarounds.erasure( typeUtils, typeMirror ), + typeFactory, + accessorNaming, + typeUtils.erasure( typeMirror ), typeElement, typeParameters, implementationType, @@ -326,25 +659,234 @@ public Type erasure() { isIterableType, isCollectionType, isMapType, - isImported + isStream, + toBeImportedTypes, + notToBeImportedTypes, + isToBeImported, + isLiteral, + loggingVerbose + ); + } + + public Type replaceSuperBoundWith( Type compare, Type replacement ) { + if ( typeParameters.isEmpty() ) { + return this; + } + List targetTypeParameters = compare.getTypeParameters(); + if ( targetTypeParameters.size() != typeParameters.size() ) { + return this; + } + TypeMirror replacementMirror = replacement.getTypeMirror(); + boolean noChange = true; + List bounds = new ArrayList<>( typeParameters.size() ); + TypeMirror[] mirrors = new TypeMirror[ typeParameters.size() ]; + for ( int x = 0; x < typeParameters.size(); x++ ) { + Type type = typeParameters.get( x ); + if ( !type.hasSuperBound() || type.isRawAssignableTo( targetTypeParameters.get( x ) ) ) { + bounds.add( type ); + mirrors[ x ] = type.getTypeMirror(); + } + else { + bounds.add( replacement ); + mirrors[x] = replacementMirror; + noChange = false; + } + } + + if ( noChange ) { + return this; + } + + TypeElement typeElementWithoutBounds; + Type componentTypeWithoutBounds; + TypeMirror typeMirrorWithoutBounds; + if ( this.componentType != null ) { + typeElementWithoutBounds = null; + componentTypeWithoutBounds = this.componentType.replaceSuperBoundWith( compare, replacement ); + typeMirrorWithoutBounds = typeUtils.getArrayType( componentTypeWithoutBounds.getTypeMirror() ); + } + else { + DeclaredType declaredType = typeUtils.getDeclaredType( + typeElement, + mirrors + ); + typeMirrorWithoutBounds = declaredType; + typeElementWithoutBounds = (TypeElement) declaredType.asElement(); + componentTypeWithoutBounds = null; + } + + return new Type( + typeUtils, + elementUtils, + typeFactory, + accessorNaming, + typeMirrorWithoutBounds, + typeElementWithoutBounds, + bounds, + implementationType, + componentTypeWithoutBounds, + packageName, + name, + qualifiedName, + isInterface, + isEnumType, + isIterableType, + isCollectionType, + isMapType, + isStream, + toBeImportedTypes, + notToBeImportedTypes, + isToBeImported, + isLiteral, + loggingVerbose + ); + } + + public Type withoutBounds() { + if ( typeParameters.isEmpty() ) { + return this; + } + + List bounds = new ArrayList<>( typeParameters.size() ); + List mirrors = new ArrayList<>( typeParameters.size() ); + for ( Type typeParameter : typeParameters ) { + bounds.add( typeParameter.getTypeBound() ); + mirrors.add( typeParameter.getTypeBound().getTypeMirror() ); + } + + TypeElement typeElementWithoutBounds; + Type componentTypeWithoutBounds; + TypeMirror typeMirrorWithoutBounds; + if ( this.componentType != null ) { + typeElementWithoutBounds = null; + componentTypeWithoutBounds = this.componentType.withoutBounds(); + typeMirrorWithoutBounds = typeUtils.getArrayType( componentTypeWithoutBounds.getTypeMirror() ); + } + else { + DeclaredType declaredType = typeUtils.getDeclaredType( + typeElement, + mirrors.toArray( new TypeMirror[] {} ) + ); + typeMirrorWithoutBounds = declaredType; + typeElementWithoutBounds = (TypeElement) declaredType.asElement(); + componentTypeWithoutBounds = null; + } + + return new Type( + typeUtils, + elementUtils, + typeFactory, + accessorNaming, + typeMirrorWithoutBounds, + typeElementWithoutBounds, + bounds, + implementationType, + componentTypeWithoutBounds, + packageName, + name, + qualifiedName, + isInterface, + isEnumType, + isIterableType, + isCollectionType, + isMapType, + isStream, + toBeImportedTypes, + notToBeImportedTypes, + isToBeImported, + isLiteral, + loggingVerbose ); } + private Type replaceGeneric(Type oldGenericType, Type newType) { + if ( !typeParameters.contains( oldGenericType ) || newType == null || oldGenericType.equals( newType ) ) { + return this; + } + newType = newType.getBoxedEquivalent(); + TypeMirror[] replacedTypeMirrors = new TypeMirror[typeParameters.size()]; + for ( int i = 0; i < typeParameters.size(); i++ ) { + Type typeParameter = typeParameters.get( i ); + replacedTypeMirrors[i] = + typeParameter.equals( oldGenericType ) ? newType.typeMirror : typeParameter.typeMirror; + } + + return typeFactory.getType( typeUtils.getDeclaredType( typeElement, replacedTypeMirrors ) ); + } + /** - * Whether this type is assignable to the given other type. + * Whether this type is assignable to the given other type, considering the "extends / upper bounds" + * as well. * * @param other The other type. * * @return {@code true} if and only if this type is assignable to the given other type. */ - // TODO This doesn't yet take wild card types into account; e.g. ? extends Integer wouldn't be assignable to Number - // atm. public boolean isAssignableTo(Type other) { + TypeMirror otherMirror = other.typeMirror; + if ( otherMirror.getKind() == TypeKind.WILDCARD ) { + otherMirror = typeUtils.erasure( other.typeMirror ); + } + if ( TypeKind.WILDCARD == typeMirror.getKind() ) { + return typeUtils.contains( typeMirror, otherMirror ); + } + return typeUtils.isAssignable( typeMirror, otherMirror ); + } + + /** + * Whether this type is raw assignable to the given other type. We can't make a verdict on typevars, + * they need to be resolved first. + * + * @param other The other type. + * + * @return {@code true} if and only if this type is assignable to the given other type. + */ + public boolean isRawAssignableTo(Type other) { + if ( isTypeVar() || other.isTypeVar() ) { + return true; + } if ( equals( other ) ) { return true; } + return typeUtils.isAssignable( typeUtils.erasure( typeMirror ), typeUtils.erasure( other.typeMirror ) ); + } + + /** + * removes any bounds from this type. + * @return the raw type + */ + public Type asRawType() { + if ( getTypeBound() != null ) { + return typeFactory.getType( typeUtils.erasure( typeMirror ) ); + } + else { + return this; + } + } + + public ReadAccessor getReadAccessor(String propertyName, boolean allowedMapToBean) { + if ( allowedMapToBean && hasStringMapSignature() ) { + ExecutableElement getMethod = getAllMethods() + .stream() + .filter( m -> m.getSimpleName().contentEquals( "get" ) ) + .filter( m -> m.getParameters().size() == 1 ) + .findAny() + .orElse( null ); + return new MapValueAccessor( getMethod, typeParameters.get( 1 ).getTypeMirror(), propertyName ); + } + + Map readAccessors = getPropertyReadAccessors(); + + return readAccessors.get( propertyName ); + } + + public PresenceCheckAccessor getPresenceChecker(String propertyName) { + if ( hasStringMapSignature() ) { + return PresenceCheckAccessor.mapContainsKey( propertyName ); + } - return typeUtils.isAssignable( typeMirror, other.typeMirror ); + Map presenceCheckers = getPropertyPresenceCheckers(); + return presenceCheckers.get( propertyName ); } /** @@ -352,16 +894,73 @@ public boolean isAssignableTo(Type other) { * * @return an unmodifiable map of all read accessors (including 'is' for booleans), indexed by property name */ - public Map getPropertyReadAccessors() { - if ( getters == null ) { - List getterList = Filters.getterMethodsIn( getAllExecutables() ); - Map modifiableGetters = new LinkedHashMap(); - for (ExecutableElement getter : getterList) { - modifiableGetters.put( Executables.getPropertyName( getter ), getter ); + public Map getPropertyReadAccessors() { + if ( readAccessors == null ) { + + Map recordAccessors = filters.recordAccessorsIn( getRecordComponents() ); + Map modifiableGetters = new LinkedHashMap<>(recordAccessors); + + List getterList = filters.getterMethodsIn( getAllMethods() ); + for ( ReadAccessor getter : getterList ) { + String simpleName = getter.getSimpleName(); + if ( recordAccessors.containsKey( simpleName ) ) { + // If there is already a record accessor that contains the simple name + // then it means that the getter is actually a record component. + // In that case we need to ignore it. + // e.g. record component named isActive. + // The DefaultAccessorNamingStrategy will return active as property name, + // but the property name is isActive, since it is a record + continue; + } + String propertyName = getPropertyName( getter ); + + if ( recordAccessors.containsKey( propertyName ) ) { + // If there is already a record accessor, the property needs to be ignored + continue; + } + if ( modifiableGetters.containsKey( propertyName ) ) { + // In the DefaultAccessorNamingStrategy, this can only be the case for Booleans: isFoo() and + // getFoo(); The latter is preferred. + if ( !simpleName.startsWith( "is" ) ) { + modifiableGetters.put( propertyName, getter ); + } + + } + else { + modifiableGetters.put( propertyName, getter ); + } + } + + List fieldsList = filters.fieldsIn( getAllFields(), ReadAccessor::fromField ); + for ( ReadAccessor field : fieldsList ) { + String propertyName = getPropertyName( field ); + // If there was no getter or is method for booleans, then resort to the field. + // If a field was already added do not add it again. + modifiableGetters.putIfAbsent( propertyName, field ); + } + readAccessors = Collections.unmodifiableMap( modifiableGetters ); + } + return readAccessors; + } + + /** + * getPropertyPresenceCheckers + * + * @return an unmodifiable map of all presence checkers, indexed by property name + */ + public Map getPropertyPresenceCheckers() { + if ( presenceCheckers == null ) { + List checkerList = filters.presenceCheckMethodsIn( getAllMethods() ); + Map modifiableCheckers = new LinkedHashMap<>(); + for ( ExecutableElement checker : checkerList ) { + modifiableCheckers.put( + getPropertyName( checker ), + PresenceCheckAccessor.methodInvocation( checker ) + ); } - getters = Collections.unmodifiableMap( modifiableGetters ); + presenceCheckers = Collections.unmodifiableMap( modifiableCheckers ); } - return getters; + return presenceCheckers; } /** @@ -369,7 +968,7 @@ public Map getPropertyReadAccessors() { * accessors include: *

      *
    • setters, the obvious candidate :-), {@link #getSetters() }
    • - *
    • getters, for collections that do not have a setter, e.g. for JAXB generated collection attributes + *
    • readAccessors, for collections that do not have a setter, e.g. for JAXB generated collection attributes * {@link #getPropertyReadAccessors() }
    • *
    • adders, typically for from table generated entities, {@link #getAdders() }
    • *
    @@ -377,122 +976,232 @@ public Map getPropertyReadAccessors() { * @param cmStrategy collection mapping strategy * @return an unmodifiable map of all write accessors indexed by property name */ - public Map getPropertyWriteAccessors( CollectionMappingStrategyPrism cmStrategy ) { - + public Map getPropertyWriteAccessors( CollectionMappingStrategyGem cmStrategy ) { + if ( isRecord() ) { + // Records do not have setters, so we return an empty map + return Collections.emptyMap(); + } // collect all candidate target accessors - List candidates = new ArrayList(); - candidates.addAll( getSetters() ); + List candidates = new ArrayList<>( getSetters() ); candidates.addAll( getAlternativeTargetAccessors() ); - Map result = new LinkedHashMap(); + Map result = new LinkedHashMap<>(); + + for ( Accessor candidate : candidates ) { + String targetPropertyName = getPropertyName( candidate ); - for ( ExecutableElement candidate : candidates ) { - String targetPropertyName = Executables.getPropertyName( candidate ); + Accessor readAccessor = getPropertyReadAccessors().get( targetPropertyName ); + + Type preferredType = determinePreferredType( readAccessor ); + Type targetType = determineTargetType( candidate ); // A target access is in general a setter method on the target object. However, in case of collections, // the current target accessor can also be a getter method. // The following if block, checks if the target accessor should be overruled by an add method. - if ( cmStrategy == CollectionMappingStrategyPrism.SETTER_PREFERRED - || cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED ) { + if ( cmStrategy == CollectionMappingStrategyGem.SETTER_PREFERRED + || cmStrategy == CollectionMappingStrategyGem.ADDER_PREFERRED + || cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE ) { // first check if there's a setter method. - ExecutableElement adderMethod = null; - if ( Executables.isSetterMethod( candidate ) ) { - Type targetType = typeFactory.getSingleParameter( typeElement, candidate ).getType(); + Accessor adderMethod = null; + if ( candidate.getAccessorType() == AccessorType.SETTER // ok, the current accessor is a setter. So now the strategy determines what to use - if ( cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED ) { - adderMethod = getAdderForType( targetType, targetPropertyName ); - } + && cmStrategy == CollectionMappingStrategyGem.ADDER_PREFERRED ) { + adderMethod = getAdderForType( targetType, targetPropertyName ); } - else if ( Executables.isGetterMethod( candidate ) ) { - // the current accessor is a getter (no setter available). But still, an add method is according + else if ( candidate.getAccessorType() == AccessorType.GETTER ) { + // the current accessor is a getter (no setter available). But still, an add method is according // to the above strategy (SETTER_PREFERRED || ADDER_PREFERRED) preferred over the getter. - Type targetType = typeFactory.getReturnType( typeFactory.getMethodType( typeElement, candidate ) ); adderMethod = getAdderForType( targetType, targetPropertyName ); } if ( adderMethod != null ) { // an adder has been found (according strategy) so overrule current choice. candidate = adderMethod; } + + } + else if ( candidate.getAccessorType() == AccessorType.FIELD && ( Executables.isFinal( candidate ) || + result.containsKey( targetPropertyName ) ) ) { + // if the candidate is a field and a mapping already exists, then use that one, skip it. + continue; + } + + if ( candidate.getAccessorType() == AccessorType.GETTER ) { + // When the candidate is a getter then it can't be used in the following cases: + // 1. The collection mapping strategy is target immutable + // 2. The target type is a stream (streams are immutable) + if ( cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE || + targetType != null && targetType.isStreamType() ) { + continue; + } } - result.put( targetPropertyName, candidate ); + Accessor previousCandidate = result.get( targetPropertyName ); + if ( previousCandidate == null || preferredType == null || ( targetType != null + && typeUtils.isAssignable( preferredType.getTypeMirror(), targetType.getTypeMirror() ) ) ) { + result.put( targetPropertyName, candidate ); + } } + return result; } - private List getAllExecutables() { - if ( allExecutables == null ) { - allExecutables = Executables.getAllEnclosedExecutableElements( elementUtils, typeElement ); + public List getRecordComponents() { + if ( recordComponents == null ) { + recordComponents = nullSafeTypeElementListConversion( filters::recordComponentsIn ); } - return allExecutables; + return recordComponents; } - /** - * Tries to find an addMethod in this type for given collection property in this type. - * - * Matching occurs on: - *
      - *
    1. The generic type parameter type of the collection should match the adder method argument
    2. - *
    3. When there are more candidates, property name is made singular (as good as is possible). This routine - * looks for a matching add method name.
    4. - *
    5. The singularization rules of Dali are used to make a property name singular. This routine - * looks for a matching add method name.
    6. - *
    - * - * @param collectionProperty property type (assumed collection) to find the adder method for - * @param pluralPropertyName the property name (assumed plural) - * - * @return corresponding adder method for getter when present - */ - private ExecutableElement getAdderForType(Type collectionProperty, String pluralPropertyName) { - - List candidates = new ArrayList(); - if ( collectionProperty.isCollectionType ) { - - // this is a collection, so this can be done always - if ( !collectionProperty.getTypeParameters().isEmpty() ) { - // there's only one type arg to a collection - TypeMirror typeArg = collectionProperty.getTypeParameters().get( 0 ).getTypeMirror(); - // now, look for a method that - // 1) starts with add, - // 2) and has typeArg as one and only arg - List adderList = getAdders(); - for ( ExecutableElement adder : adderList ) { - VariableElement arg = adder.getParameters().get( 0 ); - if ( arg.asType().equals( typeArg ) ) { - candidates.add( adder ); - } - } - } + private Type determinePreferredType(Accessor readAccessor) { + if ( readAccessor != null ) { + return typeFactory.getReturnType( (DeclaredType) typeMirror, readAccessor ); + } + return null; + } + + private Type determineTargetType(Accessor candidate) { + Parameter parameter = typeFactory.getSingleParameter( (DeclaredType) typeMirror, candidate ); + if ( parameter != null ) { + return parameter.getType(); + } + else if ( candidate.getAccessorType() == AccessorType.GETTER + || candidate.getAccessorType().isFieldAssignment() ) { + return typeFactory.getReturnType( (DeclaredType) typeMirror, candidate ); + } + return null; + } + + private List getAllMethods() { + if ( allMethods == null ) { + allMethods = nullSafeTypeElementListConversion( elementUtils::getAllEnclosedExecutableElements ); + } + + return allMethods; + } + + private List getAllFields() { + if ( allFields == null ) { + allFields = nullSafeTypeElementListConversion( elementUtils::getAllEnclosedFields ); + } + + return allFields; + } + + private List nullSafeTypeElementListConversion(Function> conversionFunction) { + if ( typeElement != null ) { + return conversionFunction.apply( typeElement ); + } + + return Collections.emptyList(); + } + + private String getPropertyName(Accessor accessor ) { + Element accessorElement = accessor.getElement(); + if ( accessorElement instanceof ExecutableElement ) { + return getPropertyName( (ExecutableElement) accessorElement ); + } + else { + return accessor.getSimpleName(); + } + } + + private String getPropertyName(ExecutableElement element) { + return accessorNaming.getPropertyName( element ); + } + + /** + * Tries to find an addMethod in this type for given collection property in this type. + * + * Matching occurs on: + *
      + *
    1. The generic type parameter type of the collection should match the adder method argument
    2. + *
    3. When there are more candidates, property name is made singular (as good as is possible). This routine + * looks for a matching add method name.
    4. + *
    5. The singularization rules of Dali are used to make a property name singular. This routine + * looks for a matching add method name.
    6. + *
    + * + * @param collectionProperty property type (assumed collection) to find the adder method for + * @param pluralPropertyName the property name (assumed plural) + * + * @return corresponding adder method for getter when present + */ + private Accessor getAdderForType(Type collectionProperty, String pluralPropertyName) { + + List candidates; + + if ( collectionProperty.isCollectionType() ) { + candidates = getAccessorCandidates( collectionProperty, Iterable.class ); } + else if ( collectionProperty.isStreamType() ) { + candidates = getAccessorCandidates( collectionProperty, Stream.class ); + } + else { + return null; + } + if ( candidates.isEmpty() ) { return null; } - else if ( candidates.size() == 1 ) { + + if ( candidates.size() == 1 ) { return candidates.get( 0 ); } - else { - for ( ExecutableElement candidate : candidates ) { - String elementName = Executables.getElementNameForAdder( candidate ); - if ( elementName.equals( Nouns.singularize( pluralPropertyName ) ) ) { - return candidate; - } + + for ( Accessor candidate : candidates ) { + String elementName = accessorNaming.getElementNameForAdder( candidate ); + if ( elementName != null && elementName.equals( Nouns.singularize( pluralPropertyName ) ) ) { + return candidate; } } return null; } + /** + * Returns all accessor candidates that start with "add" and have exactly one argument + * whose type matches the collection or stream property's type argument. + * + * @param property the collection or stream property + * @param superclass the superclass to use for type argument lookup + * + * @return accessor candidates + */ + private List getAccessorCandidates(Type property, Class superclass) { + TypeMirror typeArg = first( property.determineTypeArguments( superclass ) ).getTypeBound().getTypeMirror(); + // now, look for a method that + // 1) starts with add, + // 2) and has typeArg as one and only arg + List adderList = getAdders(); + List candidateList = new ArrayList<>(); + for ( Accessor adder : adderList ) { + TypeMirror adderParameterType = determineTargetType( adder ).getTypeMirror(); + if ( typeUtils.isSameType( boxed( adderParameterType ), boxed( typeArg ) ) ) { + candidateList.add( adder ); + } + } + return candidateList; + } + + private TypeMirror boxed(TypeMirror possiblePrimitive) { + if ( possiblePrimitive.getKind().isPrimitive() ) { + return typeUtils.boxedClass( (PrimitiveType) possiblePrimitive ).asType(); + } + else { + return possiblePrimitive; + } + } + /** * getSetters * * @return an unmodifiable list of all setters */ - private List getSetters() { + public List getSetters() { if ( setters == null ) { - setters = Collections.unmodifiableList( Filters.setterMethodsIn( getAllExecutables() ) ); + setters = Collections.unmodifiableList( filters.setterMethodsIn( getAllMethods() ) ); } return setters; } @@ -505,9 +1214,9 @@ private List getSetters() { * * @return an unmodifiable list of all adders */ - private List getAdders() { + private List getAdders() { if ( adders == null ) { - adders = Collections.unmodifiableList( Filters.adderMethodsIn( getAllExecutables() ) ); + adders = Collections.unmodifiableList( filters.adderMethodsIn( getAllMethods() ) ); } return adders; } @@ -521,22 +1230,34 @@ private List getAdders() { * * @return an unmodifiable list of alternative target accessors. */ - private List getAlternativeTargetAccessors() { + private List getAlternativeTargetAccessors() { + if ( alternativeTargetAccessors != null ) { + return alternativeTargetAccessors; + } + + if ( isRecord() ) { + alternativeTargetAccessors = Collections.emptyList(); + } if ( alternativeTargetAccessors == null ) { - List result = new ArrayList(); - List setterMethods = getSetters(); - List getterMethods = - new ArrayList( getPropertyReadAccessors().values() ); + List result = new ArrayList<>(); + List setterMethods = getSetters(); + List readAccessors = new ArrayList<>( getPropertyReadAccessors().values() ); + // All the fields are also alternative accessors + readAccessors.addAll( filters.fieldsIn( getAllFields(), ElementAccessor::new ) ); - // there could be a getter method for a list/map that is not present as setter. - // a getter could substitute the setter in that case and act as setter. + // there could be a read accessor (field or method) for a list/map that is not present as setter. + // an accessor could substitute the setter in that case and act as setter. // (assuming it is initialized) - for ( ExecutableElement getterMethod : getterMethods ) { - if ( isCollectionOrMap( getterMethod ) && - !correspondingSetterMethodExists( getterMethod, setterMethods ) ) { - result.add( getterMethod ); + for ( Accessor readAccessor : readAccessors ) { + if ( isCollectionOrMapOrStream( readAccessor ) && + !correspondingSetterMethodExists( readAccessor, setterMethods ) ) { + result.add( readAccessor ); + } + else if ( readAccessor.getAccessorType() == AccessorType.FIELD && + !correspondingSetterMethodExists( readAccessor, setterMethods ) ) { + result.add( readAccessor ); } } @@ -545,12 +1266,12 @@ private List getAlternativeTargetAccessors() { return alternativeTargetAccessors; } - private boolean correspondingSetterMethodExists(ExecutableElement getterMethod, - List setterMethods) { - String getterPropertyName = Executables.getPropertyName( getterMethod ); + private boolean correspondingSetterMethodExists(Accessor getterMethod, + List setterMethods) { + String getterPropertyName = getPropertyName( getterMethod ); - for ( ExecutableElement setterMethod : setterMethods ) { - String setterPropertyName = Executables.getPropertyName( setterMethod ); + for ( Accessor setterMethod : setterMethods ) { + String setterPropertyName = getPropertyName( setterMethod ); if ( getterPropertyName.equals( setterPropertyName ) ) { return true; } @@ -559,23 +1280,29 @@ private boolean correspondingSetterMethodExists(ExecutableElement getterMethod, return false; } - private boolean isCollectionOrMap(ExecutableElement getterMethod) { - return isCollection( getterMethod.getReturnType() ) || isMap( getterMethod.getReturnType() ); + private boolean isCollectionOrMapOrStream(Accessor getterMethod) { + return isCollection( getterMethod.getAccessedType() ) || isMap( getterMethod.getAccessedType() ) || + isStream( getterMethod.getAccessedType() ); } private boolean isCollection(TypeMirror candidate) { return isSubType( candidate, Collection.class ); } + private boolean isStream(TypeMirror candidate) { + TypeElement streamTypeElement = elementUtils.getTypeElement( JavaStreamConstants.STREAM_FQN ); + TypeMirror streamType = streamTypeElement == null ? null : typeUtils.erasure( streamTypeElement.asType() ); + return streamType != null && typeUtils.isSubtypeErased( candidate, streamType ); + } + private boolean isMap(TypeMirror candidate) { return isSubType( candidate, Map.class ); } private boolean isSubType(TypeMirror candidate, Class clazz) { String className = clazz.getCanonicalName(); - TypeMirror classType = - SpecificCompilerWorkarounds.erasure( typeUtils, elementUtils.getTypeElement( className ).asType() ); - return SpecificCompilerWorkarounds.isSubType( typeUtils, candidate, classType ); + TypeMirror classType = typeUtils.erasure( elementUtils.getTypeElement( className ).asType() ); + return typeUtils.isSubtypeErased( candidate, classType ); } /** @@ -637,6 +1364,10 @@ else if ( !method.getModifiers().contains( Modifier.PUBLIC ) ) { * FTL. */ public String getNull() { + if ( isOptionalType() ) { + return createReferenceName() + ".empty()"; + } + if ( !isPrimitive() || isArrayType() ) { return "null"; } @@ -647,10 +1378,11 @@ public String getNull() { return "0"; } if ( "char".equals( getName() ) ) { - return "'\\u0000'"; + //"'\u0000'" would have been better, but depends on platform encoding + return "0"; } if ( "double".equals( getName() ) ) { - return "0.0"; + return "0.0d"; } if ( "float".equals( getName() ) ) { return "0.0f"; @@ -667,12 +1399,31 @@ public String getNull() { throw new UnsupportedOperationException( getName() ); } + public String getSensibleDefault() { + if ( isPrimitive() ) { + return getNull(); + } + else if ( "String".equals( getName() ) ) { + return "\"\""; + } + else { + if ( isNative() ) { + // must be boxed, since primitive is already checked + return typeFactory.getType( typeUtils.unboxedType( typeMirror ) ).getNull(); + } + } + return null; + } + @Override public int hashCode() { + // javadoc typemirror: "Types should be compared using the utility methods in Types. There is no guarantee + // that any particular type will always be represented by the same object." This is true when the objects + // are in another jar than the mapper. So the qualfiedName is a better candidate. final int prime = 31; int result = 1; - result = prime * result + ( ( name == null ) ? 0 : name.hashCode() ); - result = prime * result + ( ( packageName == null ) ? 0 : packageName.hashCode() ); + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((packageName == null) ? 0 : packageName.hashCode()); return result; } @@ -689,7 +1440,14 @@ public boolean equals(Object obj) { } Type other = (Type) obj; - return typeUtils.isSameType( typeMirror, other.typeMirror ); + if ( this.isWildCardBoundByTypeVar() && other.isWildCardBoundByTypeVar() ) { + return ( this.hasExtendsBound() == other.hasExtendsBound() + || this.hasSuperBound() == other.hasSuperBound() ) + && typeUtils.isSameType( getTypeBound().getTypeMirror(), other.getTypeBound().getTypeMirror() ); + } + else { + return typeUtils.isSameType( typeMirror, other.typeMirror ); + } } @Override @@ -702,6 +1460,35 @@ public String toString() { return typeMirror.toString(); } + /** + * @return a string representation of the type for use in messages + */ + public String describe() { + if ( loggingVerbose ) { + return toString(); + } + else { + // name allows for inner classes + String name = getNameKeepingInnerClasses(); + List typeParams = getTypeParameters(); + if ( typeParams.isEmpty() ) { + return name; + } + else { + String params = typeParams.stream().map( Type::describe ).collect( Collectors.joining( "," ) ); + return String.format( "%s<%s>", name, params ); + } + } + } + + private String getNameKeepingInnerClasses() { + String packageNamePrefix = getPackageName() + "."; + String fullyQualifiedName = getFullyQualifiedName(); + if ( fullyQualifiedName.startsWith( packageNamePrefix ) ) { + return fullyQualifiedName.substring( packageNamePrefix.length() ); + } + return fullyQualifiedName; + } /** * @@ -719,42 +1506,710 @@ public String getIdentification() { /** * Establishes the type bound: *
      - *
    1. {@code}, returns Number
    2. - *
    3. {@code}, returns Number
    4. - *
    5. {@code}, returns Object
    6. - *
    7. {@code, returns Number}
    8. + *
    9. {@code }, returns Number
    10. + *
    11. {@code }, returns Number
    12. + *
    13. {@code }, returns Object
    14. + *
    15. {@code , returns Number}
    16. *
    * @return the bound for this parameter */ public Type getTypeBound() { - if ( boundingBase != null ) { return boundingBase; } - boundingBase = this; - if ( typeMirror.getKind() == TypeKind.WILDCARD ) { - WildcardType wildCardType = (WildcardType) typeMirror; - if ( wildCardType.getExtendsBound() != null ) { - boundingBase = typeFactory.getType( wildCardType.getExtendsBound() ); + + boundingBase = typeFactory.getType( typeFactory.getTypeBound( getTypeMirror() ) ); + + return boundingBase; + } + + public List getTypeBounds() { + if ( this.boundTypes != null ) { + return boundTypes; + } + Type bound = getTypeBound(); + if ( bound == null ) { + this.boundTypes = Collections.emptyList(); + } + else if ( !bound.isIntersection() ) { + this.boundTypes = Collections.singletonList( bound ); + } + else { + List bounds = ( (IntersectionType) bound.typeMirror ).getBounds(); + this.boundTypes = new ArrayList<>( bounds.size() ); + for ( TypeMirror mirror : bounds ) { + boundTypes.add( typeFactory.getType( mirror ) ); + } + } + + return this.boundTypes; + + } + + public boolean hasAccessibleConstructor() { + if ( hasAccessibleConstructor == null ) { + hasAccessibleConstructor = false; + List constructors = ElementFilter.constructorsIn( typeElement.getEnclosedElements() ); + for ( ExecutableElement constructor : constructors ) { + if ( !constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + hasAccessibleConstructor = true; + break; + } + } + } + return hasAccessibleConstructor; + } + + /** + * Whether this type can be instantiated through an accessible parameterless constructor, i.e. via + * {@code new Type()}. Unlike {@link #hasAccessibleConstructor()} this returns {@code false} for types that + * only expose constructors with parameters (e.g. {@code BigDecimal}). + * + * @return {@code true} if a {@code new Type()} call would compile, {@code false} otherwise + */ + public boolean hasAccessibleParameterlessConstructor() { + if ( typeElement == null || isInterface() || isAbstract() || isEnumType() ) { + return false; + } + List constructors = ElementFilter.constructorsIn( typeElement.getEnclosedElements() ); + if ( constructors.isEmpty() ) { + // no declared constructors means the implicit, accessible default constructor is available + return true; + } + for ( ExecutableElement constructor : constructors ) { + if ( !constructor.getModifiers().contains( Modifier.PRIVATE ) && constructor.getParameters().isEmpty() ) { + return true; + } + } + return false; + } + + public KotlinMetadata getKotlinMetadata() { + if ( !kotlinMetadataInitialized ) { + kotlinMetadataInitialized = true; + if ( typeElement != null && KOTLIN_METADATA_JVM_PRESENT ) { + Metadata metadataAnnotation = typeElement.getAnnotation( Metadata.class ); + if ( metadataAnnotation != null ) { + KotlinClassMetadata classMetadata = KotlinClassMetadata.readLenient( metadataAnnotation ); + if ( classMetadata instanceof KotlinClassMetadata.Class ) { + kotlinMetadata = new KotlinMetadataImpl( (KotlinClassMetadata.Class) classMetadata ); + } + } + } + } + + return kotlinMetadata; + } + + /** + * Returns the direct supertypes of a type. The interface types, if any, + * will appear last in the list. + * + * @return the direct supertypes, or an empty list if none + */ + public List getDirectSuperTypes() { + return typeUtils.directSupertypes( typeMirror ) + .stream() + .map( typeFactory::getType ) + .collect( Collectors.toList() ); + } + + /** + * Searches for the given superclass and collects all type arguments for the given class + * + * @param superclass the superclass or interface the generic type arguments are searched for + * @return a list of type arguments or null, if superclass was not found + */ + public List determineTypeArguments(Class superclass) { + if ( qualifiedName.equals( superclass.getName() ) ) { + return getTypeParameters(); + } + + List directSupertypes = typeUtils.directSupertypes( typeMirror ); + for ( TypeMirror supertypemirror : directSupertypes ) { + Type supertype = typeFactory.getType( supertypemirror ); + List supertypeTypeArguments = supertype.determineTypeArguments( superclass ); + if ( supertypeTypeArguments != null ) { + return supertypeTypeArguments; + } + } + + return null; + } + + /** + * All primitive types and their corresponding boxed types are considered native. + * @return true when native. + */ + public boolean isNative() { + return NativeTypes.isNative( qualifiedName ); + } + + public boolean isLiteral() { + return isLiteral; + } + + /** + * Steps through the declaredType in order to find a match for this typeVar Type. It aligns with + * the provided parameterized type where this typeVar type is used.
    + *
    + * For example:
    +     * {@code
    +     * this: T
    +     * declaredType: JAXBElement
    +     * parameterizedType: JAXBElement
    +     * result: String
    +     *
    +     *
    +     * this: T, T[] or ? extends T,
    +     * declaredType: E.g. Callable
    +     * parameterizedType: Callable
    +     * return: BigDecimal
    +     * }
    +     * 
    + * + * @param declared the type + * @param parameterized the parameterized type + * + * @return - the same type when this is not a type var in the broadest sense (T, T[], or ? extends T)
    + * - the matching parameter in the parameterized type when this is a type var when found
    + * - null in all other cases + */ + public ResolvedPair resolveParameterToType(Type declared, Type parameterized) { + if ( isTypeVar() || isArrayTypeVar() || isWildCardBoundByTypeVar() ) { + TypeVarMatcher typeVarMatcher = new TypeVarMatcher( typeFactory, typeUtils, this ); + return typeVarMatcher.visit( parameterized.getTypeMirror(), declared ); + } + return new ResolvedPair( this, this ); + } + + /** + * Resolves generic types using the declared and parameterized types as input.
    + *
    + * For example: + *
    +     * {@code
    +     * this: T
    +     * declaredType: JAXBElement
    +     * parameterizedType: JAXBElement
    +     * result: Integer
    +     *
    +     * this: List
    +     * declaredType: JAXBElement
    +     * parameterizedType: JAXBElement
    +     * result: List
    +     *
    +     * this: List
    +     * declaredType: JAXBElement
    +     * parameterizedType: JAXBElement
    +     * result: List
    +     *
    +     * this: List>
    +     * declaredType: JAXBElement
    +     * parameterizedType: JAXBElement
    +     * result: List>
    +     * }
    +     * 
    + * It also works for partial matching.
    + *
    + * For example: + *
    +     * {@code
    +     * this: Map
    +     * declaredType: JAXBElement
    +     * parameterizedType: JAXBElement
    +     * result: Map
    +     * }
    +     * 
    + * It also works with multiple parameters at both sides.
    + *
    + * For example when reversing Key/Value for a Map: + *
    +     * {@code
    +     * this: Map
    +     * declaredType: HashMap
    +     * parameterizedType: HashMap
    +     * result: Map
    +     * }
    +     * 
    + * + * Mismatch result examples: + *
    +     * {@code
    +     * this: T
    +     * declaredType: JAXBElement
    +     * parameterizedType: JAXBElement
    +     * result: null
    +     *
    +     * this: List
    +     * declaredType: JAXBElement
    +     * parameterizedType: JAXBElement
    +     * result: List
    +     * }
    +     * 
    + * + * @param declared the type + * @param parameterized the parameterized type + * + * @return - the result of {@link #resolveParameterToType(Type, Type)} when this type itself is a type var.
    + * - the type but then with the matching type parameters replaced.
    + * - the same type when this type does not contain matching type parameters. + */ + public Type resolveGenericTypeParameters(Type declared, Type parameterized) { + if ( isTypeVar() || isArrayTypeVar() || isWildCardBoundByTypeVar() ) { + return resolveParameterToType( declared, parameterized ).getMatch(); + } + Type resultType = this; + for ( Type generic : getTypeParameters() ) { + if ( generic.isTypeVar() || generic.isArrayTypeVar() || generic.isWildCardBoundByTypeVar() ) { + ResolvedPair resolveParameterToType = generic.resolveParameterToType( declared, parameterized ); + resultType = resultType.replaceGeneric( generic, resolveParameterToType.getMatch() ); + } + else { + Type replacementType = generic.resolveParameterToType( declared, parameterized ).getMatch(); + resultType = resultType.replaceGeneric( generic, replacementType ); } - else if ( wildCardType.getSuperBound() != null ) { - boundingBase = typeFactory.getType( wildCardType.getSuperBound() ); + } + return resultType; + } + + public boolean isWildCardBoundByTypeVar() { + return ( hasExtendsBound() || hasSuperBound() ) && getTypeBound().isTypeVar(); + } + + public boolean isArrayTypeVar() { + return isArrayType() && getComponentType().isTypeVar(); + } + + private static class TypeVarMatcher extends SimpleTypeVisitor8 { + + private final TypeFactory typeFactory; + private final Type typeToMatch; + private final TypeUtils types; + + /** + * @param typeFactory factory + * @param types type utils + * @param typeToMatch the typeVar or wildcard with typeVar bound + */ + TypeVarMatcher(TypeFactory typeFactory, TypeUtils types, Type typeToMatch) { + super( new ResolvedPair( typeToMatch, null ) ); + this.typeFactory = typeFactory; + this.typeToMatch = typeToMatch; + this.types = types; + } + + @Override + public ResolvedPair visitTypeVariable(TypeVariable parameterized, Type declared) { + if ( typeToMatch.isTypeVar() && types.isSameType( parameterized, typeToMatch.getTypeMirror() ) ) { + return new ResolvedPair( typeFactory.getType( parameterized ), declared ); + } + return super.DEFAULT_VALUE; + } + + /** + * If ? extends SomeTime equals the boundary set in typeVarToMatch (NOTE: you can't compare the wildcard itself) + * then return a result; + */ + @Override + public ResolvedPair visitWildcard(WildcardType parameterized, Type declared) { + if ( typeToMatch.hasExtendsBound() && parameterized.getExtendsBound() != null + && types.isSameType( typeToMatch.getTypeBound().getTypeMirror(), parameterized.getExtendsBound() ) ) { + return new ResolvedPair( typeToMatch, declared); + } + else if ( typeToMatch.hasSuperBound() && parameterized.getSuperBound() != null + && types.isSameType( typeToMatch.getTypeBound().getTypeMirror(), parameterized.getSuperBound() ) ) { + return new ResolvedPair( typeToMatch, declared); + } + if ( parameterized.getExtendsBound() != null ) { + ResolvedPair match = visit( parameterized.getExtendsBound(), declared ); + if ( match.match != null ) { + return new ResolvedPair( typeFactory.getType( parameterized ), declared ); + } + } + else if ( parameterized.getSuperBound() != null ) { + ResolvedPair match = visit( parameterized.getSuperBound(), declared ); + if ( match.match != null ) { + return new ResolvedPair( typeFactory.getType( parameterized ), declared ); + } + + } + return super.DEFAULT_VALUE; + } + + @Override + public ResolvedPair visitArray(ArrayType parameterized, Type declared) { + if ( types.isSameType( parameterized.getComponentType(), typeToMatch.getTypeMirror() ) ) { + return new ResolvedPair( typeFactory.getType( parameterized ), declared ); + } + if ( declared.isArrayType() ) { + return visit( parameterized.getComponentType(), declared.getComponentType() ); + } + return super.DEFAULT_VALUE; + } + + @Override + public ResolvedPair visitDeclared(DeclaredType parameterized, Type declared) { + + List results = new ArrayList<>( ); + if ( parameterized.getTypeArguments().isEmpty() ) { + return super.DEFAULT_VALUE; + } + else if ( types.isSameType( types.erasure( parameterized ), types.erasure( declared.getTypeMirror() ) ) ) { + // We can't assume that the type args are the same + // e.g. List is assignable to Object + if ( parameterized.getTypeArguments().size() != declared.getTypeParameters().size() ) { + return super.visitDeclared( parameterized, declared ); + } + + // only possible to compare parameters when the types are exactly the same + for ( int i = 0; i < parameterized.getTypeArguments().size(); i++ ) { + TypeMirror parameterizedTypeArg = parameterized.getTypeArguments().get( i ); + Type declaredTypeArg = declared.getTypeParameters().get( i ); + if ( parameterizedTypeArg.getKind() == TypeKind.DECLARED && + !types.isSameType( types.erasure( parameterizedTypeArg ), + types.erasure( declaredTypeArg.getTypeMirror() ) ) ) { + // Comparable can never be assigned Comparable + // Generics enforce exact matches for declared types + return DEFAULT_VALUE; + } + ResolvedPair result = visit( parameterizedTypeArg, declaredTypeArg ); + if ( result != super.DEFAULT_VALUE ) { + results.add( result ); + } + } } else { - String wildCardName = wildCardType.toString(); - if ( "?".equals( wildCardName ) ) { - boundingBase = typeFactory.getType( Object.class ); + // Also check whether the implemented interfaces are parameterized + for ( Type declaredSuperType : declared.getDirectSuperTypes() ) { + if ( Object.class.getName().equals( declaredSuperType.getFullyQualifiedName() ) ) { + continue; + } + ResolvedPair result = visitDeclared( parameterized, declaredSuperType ); + if ( result != super.DEFAULT_VALUE ) { + results.add( result ); + } } + + for ( TypeMirror parameterizedSuper : types.directSupertypes( parameterized ) ) { + if ( isJavaLangObject( parameterizedSuper ) ) { + continue; + } + ResolvedPair result = visitDeclared( (DeclaredType) parameterizedSuper, declared ); + if ( result != super.DEFAULT_VALUE ) { + results.add( result ); + } + } + } + if ( results.isEmpty() ) { + return super.DEFAULT_VALUE; + } + else { + return results.stream().allMatch( results.get( 0 )::equals ) ? results.get( 0 ) : super.DEFAULT_VALUE; } } - else if ( typeMirror.getKind() == TypeKind.TYPEVAR ) { - TypeVariable typeVariableType = (TypeVariable) typeMirror; - if ( typeVariableType.getUpperBound() != null ) { - boundingBase = typeFactory.getType( typeVariableType.getUpperBound() ); + + private boolean isJavaLangObject(TypeMirror type) { + if ( type instanceof DeclaredType ) { + return ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName() + .contentEquals( Object.class.getName() ); } - // Lowerbounds intentionally left out: Type variables otherwise have a lower bound of NullType. + return false; + } + } + + /** + * Reflects any Resolved Pair, examples are + * T, String + * ? extends T, BigDecimal + * T[], Integer[] + */ + public static class ResolvedPair { + + public ResolvedPair(Type parameter, Type match) { + this.parameter = parameter; + this.match = match; + } + + /** + * parameter, e.g. T, ? extends T or T[] + */ + private Type parameter; + + /** + * match, e.g. String, BigDecimal, Integer[] + */ + private Type match; + + public Type getParameter() { + return parameter; + } + + public Type getMatch() { + return match; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ResolvedPair that = (ResolvedPair) o; + return Objects.equals( parameter, that.parameter ) && Objects.equals( match, that.match ); + } + + @Override + public int hashCode() { + return Objects.hash( parameter ); + } + } + + /** + * Gets the boxed equivalent type if the type is primitive, int will return Integer + * + * @return boxed equivalent + */ + public Type getBoxedEquivalent() { + if ( boxedEquivalent != null ) { + return boxedEquivalent; + } + else if ( isPrimitive() ) { + boxedEquivalent = typeFactory.getType( typeUtils.boxedClass( (PrimitiveType) typeMirror ) ); + return boxedEquivalent; + } + return this; + } + + /** + * It strips all the {@code []} from the {@code className}. + * + * E.g. + *
    +     *     trimSimpleClassName("String[][][]") -> "String"
    +     *     trimSimpleClassName("String[]") -> "String"
    +     * 
    + * + * @param className that needs to be trimmed + * + * @return the trimmed {@code className}, or {@code null} if the {@code className} was {@code null} + */ + private String trimSimpleClassName(String className) { + if ( className == null ) { + return null; + } + String trimmedClassName = className; + while ( trimmedClassName.endsWith( "[]" ) ) { + trimmedClassName = trimmedClassName.substring( 0, trimmedClassName.length() - 2 ); + } + return trimmedClassName; + } + + private static String nameWithTopLevelTypeName(TypeElement element, String name) { + if ( element == null ) { + return null; + } + if ( !element.getNestingKind().isNested() ) { + return name; + } + + Deque elements = new ArrayDeque<>(); + elements.addFirst( name ); + Element parent = element.getEnclosingElement(); + while ( parent != null && parent.getKind() != ElementKind.PACKAGE ) { + elements.addFirst( parent.getSimpleName() ); + parent = parent.getEnclosingElement(); + } + + return String.join( ".", elements ); + } + + private static Type topLevelType(TypeElement typeElement, TypeFactory typeFactory) { + if ( typeElement == null || typeElement.getNestingKind() == NestingKind.TOP_LEVEL ) { + return null; + } + + Element parent = typeElement.getEnclosingElement(); + while ( parent != null ) { + if ( parent.getEnclosingElement() != null && + parent.getEnclosingElement().getKind() == ElementKind.PACKAGE ) { + break; + } + parent = parent.getEnclosingElement(); + } + return parent == null ? null : typeFactory.getType( parent.asType() ); + } + + public boolean isEnumSet() { + return "java.util.EnumSet".equals( getFullyQualifiedName() ); + } + + /** + * return true if this type is a java 17+ sealed class + */ + public boolean isSealed() { + KotlinMetadata kotlinMetadata = getKotlinMetadata(); + if ( kotlinMetadata != null ) { + return kotlinMetadata.isSealedClass(); + } + return typeElement.getModifiers().stream().map( Modifier::name ).anyMatch( "SEALED"::equals ); + } + + /** + * return the list of permitted TypeMirrors for the java 17+ sealed class + */ + @SuppressWarnings( "unchecked" ) + public List getPermittedSubclasses() { + KotlinMetadata kotlinMetadata = getKotlinMetadata(); + if ( kotlinMetadata != null ) { + return kotlinMetadata.getPermittedSubclasses(); + } + if ( SEALED_PERMITTED_SUBCLASSES_METHOD == null ) { + return emptyList(); + } + try { + return (List) SEALED_PERMITTED_SUBCLASSES_METHOD.invoke( typeElement ); + } + catch ( IllegalAccessException | IllegalArgumentException | InvocationTargetException e ) { + return emptyList(); + } + } + + private class KotlinMetadataImpl implements KotlinMetadata { + + private final KotlinClassMetadata.Class kotlinClassMetadata; + + private KotlinMetadataImpl(KotlinClassMetadata.Class kotlinClassMetadata) { + this.kotlinClassMetadata = kotlinClassMetadata; + } + + @Override + public boolean isDataClass() { + return Attributes.isData( kotlinClassMetadata.getKmClass() ); + } + + @Override + public boolean isSealedClass() { + return Attributes.getModality( kotlinClassMetadata.getKmClass() ) == Modality.SEALED; + } + + @Override + public ExecutableElement determinePrimaryConstructor(List constructors) { + if ( constructors.size() == 1 ) { + // If we have one constructor, that this constructor is the primary one + return constructors.get( 0 ); + } + KmClass kmClass = kotlinClassMetadata.getKmClass(); + KmConstructor primaryKmConstructor = null; + for ( KmConstructor constructor : kmClass.getConstructors() ) { + if ( !Attributes.isSecondary( constructor ) ) { + primaryKmConstructor = constructor; + } + + } + + if ( primaryKmConstructor == null ) { + return null; + } + + List sameParametersSizeConstructors = new ArrayList<>(); + for ( ExecutableElement constructor : constructors ) { + if ( constructor.getParameters().size() == primaryKmConstructor.getValueParameters().size() ) { + sameParametersSizeConstructors.add( constructor ); + } + } + + if ( sameParametersSizeConstructors.size() == 1 ) { + return sameParametersSizeConstructors.get( 0 ); + } + + JvmMethodSignature signature = JvmExtensionsKt.getSignature( primaryKmConstructor ); + if ( signature == null ) { + return null; + } + + String signatureDescriptor = signature.getDescriptor(); + for ( ExecutableElement constructor : constructors ) { + String constructorDescriptor = buildJvmConstructorDescriptor( constructor ); + if ( signatureDescriptor.equals( constructorDescriptor ) ) { + return constructor; + } + } + + return null; + } + + @Override + public List getPermittedSubclasses() { + List sealedSubclassNames = kotlinClassMetadata.getKmClass().getSealedSubclasses(); + List permittedSubclasses = new ArrayList<>( sealedSubclassNames.size() ); + for ( String sealedSubclassName : sealedSubclassNames ) { + Type subclassType = typeFactory.getType( sealedSubclassName.replace( '/', '.' ) ); + permittedSubclasses.add( subclassType.getTypeMirror() ); + } + + return permittedSubclasses; + } + + private String buildJvmConstructorDescriptor(ExecutableElement constructor) { + StringBuilder signature = new StringBuilder( "(" ); + + for ( VariableElement param : constructor.getParameters() ) { + signature.append( getJvmTypeDescriptor( param.asType() ) ); + } + + signature.append( ")V" ); + return signature.toString(); + } + + private String getJvmTypeDescriptor(TypeMirror type) { + return type.accept( + new SimpleTypeVisitor8() { + @Override + public String visitPrimitive(PrimitiveType t, Void p) { + switch ( t.getKind() ) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case CHAR: + return "C"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + default: + return ""; + } + } + + @Override + public String visitDeclared(DeclaredType t, Void p) { + TypeElement element = (TypeElement) t.asElement(); + String binaryName = elementUtils.getBinaryName( element ).toString(); + return "L" + binaryName.replace( '.', '/' ) + ";"; + } + + @Override + public String visitArray(ArrayType t, Void p) { + return "[" + getJvmTypeDescriptor( t.getComponentType() ); + } + + @Override + protected String defaultAction(TypeMirror e, Void p) { + return ""; + } + }, null + ); } - return boundingBase; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index b5b5f75b72..8d79780d35 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.common; @@ -23,6 +10,8 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.NavigableMap; @@ -36,7 +25,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentNavigableMap; import java.util.concurrent.ConcurrentSkipListMap; - +import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; @@ -47,19 +36,33 @@ import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; -import javax.lang.model.util.Types; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; -import org.mapstruct.ap.internal.prism.MappingTargetPrism; -import org.mapstruct.ap.internal.prism.TargetTypePrism; +import org.mapstruct.ap.internal.gem.BuilderGem; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.Collections; -import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds; - -import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.erasure; -import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.Extractor; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.JavaCollectionConstants; +import org.mapstruct.ap.internal.util.JavaStreamConstants; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.NativeTypes; +import org.mapstruct.ap.internal.util.RoundContext; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.version.VersionInformation; +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.MoreThanOneBuilderCreationMethodException; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; + +import static org.mapstruct.ap.internal.model.common.ImplementationType.withDefaultConstructor; +import static org.mapstruct.ap.internal.model.common.ImplementationType.withFactoryMethod; +import static org.mapstruct.ap.internal.model.common.ImplementationType.withInitialCapacity; +import static org.mapstruct.ap.internal.model.common.ImplementationType.withLoadFactorAdjustment; /** * Factory creating {@link Type} instances. @@ -68,38 +71,100 @@ */ public class TypeFactory { - private final Elements elementUtils; - private final Types typeUtils; + private static final Extractor BUILDER_INFO_CREATION_METHOD_EXTRACTOR = + builderInfo -> { + ExecutableElement builderCreationMethod = builderInfo.getBuilderCreationMethod(); + + StringBuilder sb = new StringBuilder( builderCreationMethod.getSimpleName() ); + + sb.append( '(' ); + for ( VariableElement parameter : builderCreationMethod.getParameters() ) { + sb.append( parameter ); + } + + sb.append( ')' ); + return sb.toString(); + }; + private static final String LINKED_HASH_SET_FACTORY_METHOD_NAME = "newLinkedHashSet"; + private static final String LINKED_HASH_MAP_FACTORY_METHOD_NAME = "newLinkedHashMap"; + + private final ElementUtils elementUtils; + private final TypeUtils typeUtils; + private final FormattingMessager messager; + private final RoundContext roundContext; private final TypeMirror iterableType; private final TypeMirror collectionType; private final TypeMirror mapType; + private final TypeMirror streamType; - private final Map implementationTypes = new HashMap(); - private final Map importedQualifiedTypesBySimpleName = new HashMap(); + private final Map implementationTypes = new HashMap<>(); + private final Map toBeImportedTypes = new HashMap<>(); + private final Map notToBeImportedTypes; - public TypeFactory(Elements elementUtils, Types typeUtils) { + private final boolean loggingVerbose; + + public TypeFactory(ElementUtils elementUtils, TypeUtils typeUtils, FormattingMessager messager, + RoundContext roundContext, Map notToBeImportedTypes, boolean loggingVerbose, + VersionInformation versionInformation) { this.elementUtils = elementUtils; this.typeUtils = typeUtils; + this.messager = messager; + this.roundContext = roundContext; + this.notToBeImportedTypes = notToBeImportedTypes; - iterableType = erasure( typeUtils, elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() ); + iterableType = typeUtils.erasure( elementUtils.getTypeElement( Iterable.class.getCanonicalName() ).asType() ); collectionType = - erasure( typeUtils, elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType() ); - mapType = erasure( typeUtils, elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() ); - - implementationTypes.put( Iterable.class.getName(), getType( ArrayList.class ) ); - implementationTypes.put( Collection.class.getName(), getType( ArrayList.class ) ); - implementationTypes.put( List.class.getName(), getType( ArrayList.class ) ); + typeUtils.erasure( elementUtils.getTypeElement( Collection.class.getCanonicalName() ).asType() ); + mapType = typeUtils.erasure( elementUtils.getTypeElement( Map.class.getCanonicalName() ).asType() ); + TypeElement streamTypeElement = elementUtils.getTypeElement( JavaStreamConstants.STREAM_FQN ); + streamType = streamTypeElement == null ? null : typeUtils.erasure( streamTypeElement.asType() ); + + implementationTypes.put( Iterable.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); + implementationTypes.put( Collection.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); + implementationTypes.put( List.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); + + boolean sourceVersionAtLeast19 = versionInformation.isSourceVersionAtLeast19(); + implementationTypes.put( + Set.class.getName(), + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + ); + implementationTypes.put( SortedSet.class.getName(), withDefaultConstructor( getType( TreeSet.class ) ) ); + implementationTypes.put( NavigableSet.class.getName(), withDefaultConstructor( getType( TreeSet.class ) ) ); + + implementationTypes.put( + Map.class.getName(), + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + ); + implementationTypes.put( SortedMap.class.getName(), withDefaultConstructor( getType( TreeMap.class ) ) ); + implementationTypes.put( NavigableMap.class.getName(), withDefaultConstructor( getType( TreeMap.class ) ) ); + implementationTypes.put( + ConcurrentMap.class.getName(), + withLoadFactorAdjustment( getType( ConcurrentHashMap.class ) ) + ); + implementationTypes.put( + ConcurrentNavigableMap.class.getName(), + withDefaultConstructor( getType( ConcurrentSkipListMap.class ) ) + ); + implementationTypes.put( + JavaCollectionConstants.SEQUENCED_SET_FQN, + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) + ); + implementationTypes.put( + JavaCollectionConstants.SEQUENCED_MAP_FQN, + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) + ); - implementationTypes.put( Set.class.getName(), getType( HashSet.class ) ); - implementationTypes.put( SortedSet.class.getName(), getType( TreeSet.class ) ); - implementationTypes.put( NavigableSet.class.getName(), getType( TreeSet.class ) ); + this.loggingVerbose = loggingVerbose; + } - implementationTypes.put( Map.class.getName(), getType( HashMap.class ) ); - implementationTypes.put( SortedMap.class.getName(), getType( TreeMap.class ) ); - implementationTypes.put( NavigableMap.class.getName(), getType( TreeMap.class ) ); - implementationTypes.put( ConcurrentMap.class.getName(), getType( ConcurrentHashMap.class ) ); - implementationTypes.put( ConcurrentNavigableMap.class.getName(), getType( ConcurrentSkipListMap.class ) ); + public Type getTypeForLiteral(Class type) { + return type.isPrimitive() ? getType( getPrimitiveType( type ), true ) + : getType( type.getCanonicalName(), true ); } public Type getType(Class type) { @@ -107,6 +172,10 @@ public Type getType(Class type) { } public Type getType(String canonicalName) { + return getType( canonicalName, false ); + } + + private Type getType(String canonicalName, boolean isLiteral) { TypeElement typeElement = elementUtils.getTypeElement( canonicalName ); if ( typeElement == null ) { @@ -115,7 +184,7 @@ public Type getType(String canonicalName) { ); } - return getType( typeElement ); + return getType( typeElement, isLiteral ); } /** @@ -128,20 +197,55 @@ public boolean isTypeAvailable(String canonicalName) { return null != elementUtils.getTypeElement( canonicalName ); } + public Type getWrappedType(Type type ) { + Type result = type; + if ( type.isPrimitive() ) { + PrimitiveType typeMirror = (PrimitiveType) type.getTypeMirror(); + result = getType( typeUtils.boxedClass( typeMirror ) ); + } + return result; + } + public Type getType(TypeElement typeElement) { - return getType( typeElement.asType() ); + return getType( typeElement.asType(), false ); + } + + private Type getType(TypeElement typeElement, boolean isLiteral) { + return getType( typeElement.asType(), isLiteral ); } public Type getType(TypeMirror mirror) { - if ( mirror.getKind() == TypeKind.ERROR ) { - throw new AnnotationProcessingException( "Encountered erroneous type " + mirror ); + return getType( mirror, false ); + } + + /** + * Return a type that is always going to be imported. + * This is useful when using it in {@code Mapper#imports} + * for types that should be used in expressions. + * + * @param mirror the type mirror for which we need a type + * + * @return the type + */ + public Type getAlwaysImportedType(TypeMirror mirror) { + return getType( mirror, false, true ); + } + + private Type getType(TypeMirror mirror, boolean isLiteral) { + return getType( mirror, isLiteral, null ); + } + + private Type getType(TypeMirror mirror, boolean isLiteral, Boolean alwaysImport) { + if ( !canBeProcessed( mirror ) ) { + throw new TypeHierarchyErroneousException( mirror ); } - Type implementationType = getImplementationType( mirror ); + ImplementationType implementationType = getImplementationType( mirror ); - boolean isIterableType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, iterableType ); - boolean isCollectionType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, collectionType ); - boolean isMapType = SpecificCompilerWorkarounds.isSubType( typeUtils, mirror, mapType ); + boolean isIterableType = typeUtils.isSubtypeErased( mirror, iterableType ); + boolean isCollectionType = typeUtils.isSubtypeErased( mirror, collectionType ); + boolean isMapType = typeUtils.isSubtypeErased( mirror, mapType ); + boolean isStreamType = streamType != null && typeUtils.isSubtypeErased( mirror, streamType ); boolean isEnumType; boolean isInterface; @@ -150,7 +254,7 @@ public Type getType(TypeMirror mirror) { String qualifiedName; TypeElement typeElement; Type componentType; - + Boolean toBeImported = alwaysImport; if ( mirror.getKind() == TypeKind.DECLARED ) { DeclaredType declaredType = (DeclaredType) mirror; @@ -159,7 +263,7 @@ public Type getType(TypeMirror mirror) { isInterface = declaredType.asElement().getKind() == ElementKind.INTERFACE; name = declaredType.asElement().getSimpleName().toString(); - typeElement = declaredType.asElement().accept( new TypeElementRetrievalVisitor(), null ); + typeElement = (TypeElement) declaredType.asElement(); if ( typeElement != null ) { packageName = elementUtils.getPackageOf( typeElement ).getQualifiedName().toString(); @@ -174,39 +278,69 @@ public Type getType(TypeMirror mirror) { } else if ( mirror.getKind() == TypeKind.ARRAY ) { TypeMirror componentTypeMirror = getComponentType( mirror ); + StringBuilder builder = new StringBuilder("[]"); + + while ( componentTypeMirror.getKind() == TypeKind.ARRAY ) { + componentTypeMirror = getComponentType( componentTypeMirror ); + builder.append( "[]" ); + } if ( componentTypeMirror.getKind() == TypeKind.DECLARED ) { DeclaredType declaredType = (DeclaredType) componentTypeMirror; - TypeElement componentTypeElement = - declaredType.asElement().accept( new TypeElementRetrievalVisitor(), null ); + TypeElement componentTypeElement = (TypeElement) declaredType.asElement(); - name = componentTypeElement.getSimpleName().toString() + "[]"; + String arraySuffix = builder.toString(); + name = componentTypeElement.getSimpleName().toString() + arraySuffix; packageName = elementUtils.getPackageOf( componentTypeElement ).getQualifiedName().toString(); - qualifiedName = componentTypeElement.getQualifiedName().toString() + "[]"; + qualifiedName = componentTypeElement.getQualifiedName().toString() + arraySuffix; + } + else if ( componentTypeMirror.getKind().isPrimitive() ) { + // When the component type is primitive and is annotated with ElementType.TYPE_USE then + // the typeMirror#toString returns (@CustomAnnotation :: byte) for the javac compiler + name = NativeTypes.getName( componentTypeMirror.getKind() ) + builder.toString(); + packageName = null; + // for primitive types only name (e.g. byte, short..) required as qualified name + qualifiedName = name; + toBeImported = false; } else { name = mirror.toString(); packageName = null; qualifiedName = name; + toBeImported = false; } isEnumType = false; isInterface = false; typeElement = null; - componentType = getType( componentTypeMirror ); + componentType = getType( getComponentType( mirror ) ); } else { isEnumType = false; isInterface = false; - name = mirror.toString(); + // When the component type is primitive and is annotated with ElementType.TYPE_USE then + // the typeMirror#toString returns (@CustomAnnotation :: byte) for the javac compiler + if ( mirror.getKind().isPrimitive() ) { + name = NativeTypes.getName( mirror.getKind() ); + } + // When the component type is type var and is annotated with ElementType.TYPE_USE then + // the typeMirror#toString returns (@CustomAnnotation T) for the errorprone javac compiler + else if ( mirror.getKind() == TypeKind.TYPEVAR ) { + name = ( (TypeVariable) mirror ).asElement().getSimpleName().toString(); + } + else { + name = mirror.toString(); + } packageName = null; qualifiedName = name; typeElement = null; componentType = null; + toBeImported = false; } return new Type( typeUtils, elementUtils, this, + roundContext.getAnnotationProcessorContext().getAccessorNaming(), mirror, typeElement, getTypeParameters( mirror, false ), @@ -220,7 +354,12 @@ else if ( mirror.getKind() == TypeKind.ARRAY ) { isIterableType, isCollectionType, isMapType, - isImported( name, qualifiedName ) + isStreamType, + toBeImportedTypes, + notToBeImportedTypes, + toBeImported, + isLiteral, + loggingVerbose ); } @@ -255,19 +394,37 @@ else if ( type.isPrimitive() ) { * Get the ExecutableType for given method as part of usedMapper. Possibly parameterized types in method declaration * will be evaluated to concrete types then. * + * IMPORTANT: This should only be used from the Processors, as they are operating over executable elements. + * The internals should not be using this function and should not be using the {@link ExecutableElement} directly. + * * @param includingType the type on which's scope the method type shall be evaluated * @param method the method * @return the ExecutableType representing the method as part of usedMapper */ - public ExecutableType getMethodType(TypeElement includingType, ExecutableElement method) { - DeclaredType asType = (DeclaredType) replaceTypeElementIfNecessary( elementUtils, includingType ).asType(); - TypeMirror asMemberOf = typeUtils.asMemberOf( asType, method ); - ExecutableType methodType = asMemberOf.accept( new ExecutableTypeRetrievalVisitor(), null ); - return methodType; + public ExecutableType getMethodType(DeclaredType includingType, ExecutableElement method) { + TypeMirror asMemberOf = typeUtils.asMemberOf( includingType, method ); + return (ExecutableType) asMemberOf; } - public Parameter getSingleParameter(TypeElement includingType, ExecutableElement method) { - List parameters = method.getParameters(); + /** + * Get the Type for given method as part of usedMapper. Possibly parameterized types in method declaration will be + * evaluated to concrete types then. + * + * @param includingType the type on which's scope the method type shall be evaluated + * @param method the method + * + * @return the ExecutableType representing the method as part of usedMapper + */ + public TypeMirror getMethodType(DeclaredType includingType, Element method) { + return typeUtils.asMemberOf( includingType, method ); + } + + public Parameter getSingleParameter(DeclaredType includingType, Accessor method) { + if ( method.getAccessorType().isFieldAssignment() ) { + return null; + } + ExecutableElement executable = (ExecutableElement) method.getElement(); + List parameters = executable.getParameters(); if ( parameters.size() != 1 ) { //TODO: Log error @@ -277,67 +434,104 @@ public Parameter getSingleParameter(TypeElement includingType, ExecutableElement return Collections.first( getParameters( includingType, method ) ); } - public List getParameters(TypeElement includingType, ExecutableElement method) { - return getParameters( getMethodType( includingType, method ), method ); + public List getParameters(DeclaredType includingType, Accessor accessor) { + ExecutableElement method = (ExecutableElement) accessor.getElement(); + return getParameters( includingType, method ); + } + + public List getParameters(DeclaredType includingType, ExecutableElement method) { + ExecutableType methodType = getMethodType( includingType, method ); + if ( method == null || methodType.getKind() != TypeKind.EXECUTABLE ) { + return new ArrayList<>(); + } + return getParameters( methodType, method ); } public List getParameters(ExecutableType methodType, ExecutableElement method) { List parameterTypes = methodType.getParameterTypes(); List parameters = method.getParameters(); - List result = new ArrayList( parameters.size() ); + List result = new ArrayList<>( parameters.size() ); Iterator varIt = parameters.iterator(); Iterator typesIt = parameterTypes.iterator(); - for ( ; varIt.hasNext(); ) { + while ( varIt.hasNext() ) { VariableElement parameter = varIt.next(); TypeMirror parameterType = typesIt.next(); - result.add( new Parameter( - parameter.getSimpleName().toString(), - getType( parameterType ), - MappingTargetPrism.getInstanceOn( parameter ) != null, - TargetTypePrism.getInstanceOn( parameter ) != null ) ); + Type type = getType( parameterType ); + + // if the method has varargs and this is the last parameter + // we know that this parameter should be used as varargs + boolean isVarArgs = !varIt.hasNext() && method.isVarArgs(); + + result.add( Parameter.forElementAndType( parameter, type, isVarArgs ) ); } return result; } - public Type getReturnType(TypeElement includingType, ExecutableElement method) { - return getReturnType( getMethodType( includingType, method ) ); + public Type getReturnType(DeclaredType includingType, Accessor accessor) { + Type type; + TypeMirror accessorType = getMethodType( includingType, accessor.getElement() ); + if ( isExecutableType( accessorType ) ) { + type = getType( ( (ExecutableType) accessorType ).getReturnType() ); + } + else { + type = getType( accessorType ); + } + + return type; + } + + private boolean isExecutableType(TypeMirror accessorType) { + return accessorType.getKind() == TypeKind.EXECUTABLE; } public Type getReturnType(ExecutableType method) { return getType( method.getReturnType() ); } - public List getThrownTypes(TypeElement includingType, ExecutableElement method) { - return getThrownTypes( getMethodType( includingType, method ) ); + public List getThrownTypes(ExecutableType method) { + return extractTypes( method.getThrownTypes() ); } - public List getThrownTypes(ExecutableType method) { - List thrownTypes = new ArrayList(); - for ( TypeMirror exceptionType : method.getThrownTypes() ) { - thrownTypes.add( getType( exceptionType ) ); + public List getThrownTypes(Accessor accessor) { + if ( accessor.getAccessorType().isFieldAssignment() ) { + return new ArrayList<>(); + } + Element element = accessor.getElement(); + if ( element instanceof ExecutableElement ) { + return extractTypes( ( (ExecutableElement) element ).getThrownTypes() ); } - return thrownTypes; + return new ArrayList<>(); + } + + private List extractTypes(List typeMirrors) { + Set types = new HashSet<>( typeMirrors.size() ); + + for ( TypeMirror typeMirror : typeMirrors ) { + types.add( getType( typeMirror ) ); + } + + return new ArrayList<>( types ); } private List getTypeParameters(TypeMirror mirror, boolean isImplementationType) { + while ( mirror.getKind() == TypeKind.ARRAY ) { + mirror = getComponentType( mirror ); + } if ( mirror.getKind() != TypeKind.DECLARED ) { return java.util.Collections.emptyList(); } DeclaredType declaredType = (DeclaredType) mirror; - List typeParameters = new ArrayList( declaredType.getTypeArguments().size() ); + List typeArguments = declaredType.getTypeArguments(); + List typeParameters = new ArrayList<>( typeArguments.size() ); - for ( TypeMirror typeParameter : declaredType.getTypeArguments() ) { - if ( isImplementationType ) { - typeParameters.add( getType( typeParameter ).getTypeBound() ); - } - else { - typeParameters.add( getType( typeParameter ) ); - } + for ( TypeMirror typeParameter : typeArguments ) { + Type type = getType( typeParameter ); + typeParameters.add( isImplementationType ? type.getTypeBound() : type ); } return typeParameters; @@ -355,23 +549,25 @@ private TypeMirror getPrimitiveType(Class primitiveType) { typeUtils.getPrimitiveType( TypeKind.VOID ); } - private Type getImplementationType(TypeMirror mirror) { + private ImplementationType getImplementationType(TypeMirror mirror) { if ( mirror.getKind() != TypeKind.DECLARED ) { return null; } DeclaredType declaredType = (DeclaredType) mirror; - Type implementationType = implementationTypes.get( + ImplementationType implementation = implementationTypes.get( ( (TypeElement) declaredType.asElement() ).getQualifiedName() .toString() ); - if ( implementationType != null ) { - return new Type( + if ( implementation != null ) { + Type implementationType = implementation.getType(); + Type replacement = new Type( typeUtils, elementUtils, this, + roundContext.getAnnotationProcessorContext().getAccessorNaming(), typeUtils.getDeclaredType( implementationType.getTypeElement(), declaredType.getTypeArguments().toArray( new TypeMirror[] { } ) @@ -388,8 +584,37 @@ private Type getImplementationType(TypeMirror mirror) { implementationType.isIterableType(), implementationType.isCollectionType(), implementationType.isMapType(), - isImported( implementationType.getName(), implementationType.getFullyQualifiedName() ) + implementationType.isStreamType(), + toBeImportedTypes, + notToBeImportedTypes, + null, + implementationType.isLiteral(), + loggingVerbose ); + return implementation.createNew( replacement ); + } + + return null; + } + + private BuilderInfo findBuilder(TypeMirror type, BuilderGem builderGem, boolean report) { + if ( builderGem != null && builderGem.disableBuilder().get() ) { + return null; + } + try { + return roundContext.getAnnotationProcessorContext() + .getBuilderProvider() + .findBuilderInfo( type ); + } + catch ( MoreThanOneBuilderCreationMethodException ex ) { + if ( report ) { + messager.printMessage( + typeUtils.asElement( type ), + Message.BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD, + type, + Strings.join( ex.getBuilderInfo(), ", ", BUILDER_INFO_CREATION_METHOD_EXTRACTOR ) + ); + } } return null; @@ -404,78 +629,105 @@ private TypeMirror getComponentType(TypeMirror mirror) { return arrayType.getComponentType(); } - private boolean isImported(String name, String qualifiedName) { - String trimmedName = TypeFactory.trimSimpleClassName( name ); - String importedType = importedQualifiedTypesBySimpleName.get( trimmedName ); + /** + * creates a void return type + * + * @return void type + */ + public Type createVoidType() { + return getType( typeUtils.getNoType( TypeKind.VOID ) ); + } - boolean imported = false; - if ( importedType != null ) { - if ( importedType.equals( qualifiedName ) ) { - imported = true; + /** + * Establishes the type bound: + *
      + *
    1. {@code }, returns Number
    2. + *
    3. {@code }, returns Number
    4. + *
    5. {@code }, returns Object
    6. + *
    7. {@code , returns Number}
    8. + *
    + * + * @param typeMirror the type to return the bound for + * @return the bound for this parameter + */ + public TypeMirror getTypeBound(TypeMirror typeMirror) { + if ( typeMirror.getKind() == TypeKind.WILDCARD ) { + WildcardType wildCardType = (WildcardType) typeMirror; + if ( wildCardType.getExtendsBound() != null ) { + return wildCardType.getExtendsBound(); } - } - else { - importedQualifiedTypesBySimpleName.put( trimmedName, qualifiedName ); - imported = true; - } - return imported; - } - private static class TypeElementRetrievalVisitor extends SimpleElementVisitor6 { - @Override - public TypeElement visitType(TypeElement e, Void p) { - return e; - } - } + if ( wildCardType.getSuperBound() != null ) { + return wildCardType.getSuperBound(); + } - private static class ExecutableTypeRetrievalVisitor extends SimpleTypeVisitor6 { - @Override - public ExecutableType visitExecutable(ExecutableType t, Void p) { - return t; + String wildCardName = wildCardType.toString(); + if ( "?".equals( wildCardName ) ) { + return elementUtils.getTypeElement( Object.class.getCanonicalName() ).asType(); + } } + else if ( typeMirror.getKind() == TypeKind.TYPEVAR ) { + TypeVariable typeVariableType = (TypeVariable) typeMirror; + if ( typeVariableType.getUpperBound() != null ) { + return typeVariableType.getUpperBound(); + } + // lower bounds ( T super Number ) cannot be used for argument parameters, but can be used for + // method parameters: e.g. T map (T in); + if ( typeVariableType.getLowerBound() != null ) { + return typeVariableType.getLowerBound(); + } + } + + return typeMirror; } /** - * Converts any collection type, e.g. {@code List} to {@code Collection} and any map type, e.g. - * {@code HashMap} to {@code Map}. - * - * @param collectionOrMap any collection or map type - * @return the type representing {@code Collection} or {@code Map}, if the argument type is a subtype of - * {@code Collection} or of {@code Map} respectively. + * Whether the given type is ready to be processed or not. It can be processed if it is not of kind + * {@link TypeKind#ERROR} and all {@link AstModifyingAnnotationProcessor}s (if any) indicated that they've fully + * processed the type. */ - public Type asCollectionOrMap(Type collectionOrMap) { - List originalParameters = collectionOrMap.getTypeParameters(); - TypeMirror[] originalParameterMirrors = new TypeMirror[originalParameters.size()]; - int i = 0; - for ( Type param : originalParameters ) { - originalParameterMirrors[i++] = param.getTypeMirror(); + private boolean canBeProcessed(TypeMirror type) { + if ( type.getKind() == TypeKind.ERROR ) { + return false; } - if ( collectionOrMap.isCollectionType() - && !"java.util.Collection".equals( collectionOrMap.getFullyQualifiedName() ) ) { - return getType( typeUtils.getDeclaredType( - elementUtils.getTypeElement( "java.util.Collection" ), - originalParameterMirrors ) ); + if ( type.getKind() != TypeKind.DECLARED ) { + return true; } - else if ( collectionOrMap.isMapType() - && !"java.util.Map".equals( collectionOrMap.getFullyQualifiedName() ) ) { - return getType( typeUtils.getDeclaredType( - elementUtils.getTypeElement( "java.util.Map" ), - originalParameterMirrors ) ); + + if ( roundContext.isReadyForProcessing( type ) ) { + return true; } - return collectionOrMap; - } + List astModifyingAnnotationProcessors = roundContext + .getAnnotationProcessorContext() + .getAstModifyingAnnotationProcessors(); - static String trimSimpleClassName(String className) { - if ( className == null ) { - return null; + for ( AstModifyingAnnotationProcessor processor : astModifyingAnnotationProcessors ) { + if ( !processor.isTypeComplete( type ) ) { + return false; + } } - String trimmedClassName = className; - while ( trimmedClassName.endsWith( "[]" ) ) { - trimmedClassName = trimmedClassName.substring( 0, trimmedClassName.length() - 2 ); + + roundContext.addTypeReadyForProcessing( type ); + + return true; + } + + public BuilderType builderTypeFor( Type type, BuilderGem builder ) { + if ( type != null ) { + BuilderInfo builderInfo = findBuilder( type.getTypeMirror(), builder, true ); + return BuilderType.create( builderInfo, type, this, this.typeUtils ); } - return trimmedClassName; + return null; } + public Type effectiveResultTypeFor( Type type, BuilderGem builder ) { + if ( type != null ) { + BuilderInfo builderInfo = findBuilder( type.getTypeMirror(), builder, false ); + BuilderType builderType = BuilderType.create( builderInfo, type, this, this.typeUtils ); + return builderType != null ? builderType.getBuilder() : type; + } + return type; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/package-info.java index d83b789a15..32be1929a9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

    diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/GraphAnalyzer.java b/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/GraphAnalyzer.java index 8303ef179f..06acfc24b5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/GraphAnalyzer.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/GraphAnalyzer.java @@ -1,28 +1,15 @@ -/** - * Copyright 2012-2015 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.model.dependency; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -38,11 +25,12 @@ public class GraphAnalyzer { private final Map nodes; private final Set> cycles; private final Stack currentPath; + private int nextTraversalSequence = 0; private GraphAnalyzer(Map nodes) { this.nodes = nodes; - cycles = new HashSet>(); - currentPath = new Stack(); + cycles = new HashSet<>(); + currentPath = new Stack<>(); } public static GraphAnalyzerBuilder builder() { @@ -64,16 +52,18 @@ private void analyze() { } /** - * Returns all the descendants of the given node, either direct or transitive ones. + * Returns the traversal sequence number of the given node. The ascending order of the traversal sequence numbers of + * multiple nodes represents the depth-first traversal order of those nodes. *

    - * Note:The list will only be complete if the graph contains no cycles. + * Note: The traversal sequence numbers will only be complete if the graph contains no cycles. * - * @param name the node name to get the descendants for - * @return the descendants + * @param name the node name to get the traversal sequence number for + * @return the traversal sequence number, or {@code -1} if the node doesn't exist or the node was not visited (in + * case of cycles). */ - public Set getAllDescendants(String name) { + public int getTraversalSequence(String name) { Node node = nodes.get( name ); - return node != null ? node.getAllDescendants() : Collections.emptySet(); + return node != null ? node.getTraversalSequence() : -1; } public Set> getCycles() { @@ -98,15 +88,14 @@ private void depthFirstSearch(Node node) { for ( Node descendant : node.getDescendants() ) { depthFirstSearch( descendant ); - node.getAllDescendants().addAll( descendant.getAllDescendants() ); } - node.setProcessed( true ); + node.setTraversalSequence( nextTraversalSequence++ ); currentPath.pop(); } private List getCurrentCycle(Node start) { - List cycle = new ArrayList(); + List cycle = new ArrayList<>(); boolean inCycle = false; for ( Node n : currentPath ) { @@ -124,20 +113,20 @@ private List getCurrentCycle(Node start) { public static class GraphAnalyzerBuilder { - private final Map nodes = new HashMap(); + private final Map nodes = new LinkedHashMap<>(); - public GraphAnalyzerBuilder withNode(String name, List descendants) { - Node node = getNode( name ); + public GraphAnalyzerBuilder withNode(String name, Set descendants) { + Node node = nodes.computeIfAbsent( name, Node::new ); for ( String descendant : descendants ) { - node.addDescendant( getNode( descendant ) ); + node.addDescendant( nodes.computeIfAbsent( descendant, Node::new ) ); } return this; } public GraphAnalyzerBuilder withNode(String name, String... descendants) { - return withNode( name, Arrays.asList( descendants ) ); + return withNode( name, new LinkedHashSet<>( Arrays.asList( descendants ) ) ); } /** @@ -151,16 +140,5 @@ public GraphAnalyzer build() { graphAnalyzer.analyze(); return graphAnalyzer; } - - private Node getNode(String name) { - Node node = nodes.get( name ); - - if ( node == null ) { - node = new Node( name ); - nodes.put( name, node ); - } - - return node; - } } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/Node.java b/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/Node.java index c317bf7d19..883731ac7c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/Node.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/Node.java @@ -1,27 +1,13 @@ -/** - * Copyright 2012-2015 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.model.dependency; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Objects; /** * A node of a directed graph. @@ -32,22 +18,16 @@ class Node { private final String name; private boolean visited; - private boolean processed; + private int traversalSequence = -1; /** * The direct descendants of this node. */ private final List descendants; - /** - * All descendants of this node, direct and transitive ones, as discovered through graph traversal. - */ - private final Set allDescendants; - - public Node(String name) { + Node(String name) { this.name = name; - descendants = new ArrayList(); - allDescendants = new HashSet(); + descendants = new ArrayList<>(); } public String getName() { @@ -63,26 +43,25 @@ public void setVisited(boolean visited) { } public boolean isProcessed() { - return processed; + return traversalSequence >= 0; } - public void setProcessed(boolean processed) { - this.processed = processed; + public int getTraversalSequence() { + return traversalSequence; + } + + public void setTraversalSequence(int traversalSequence) { + this.traversalSequence = traversalSequence; } public void addDescendant(Node node) { descendants.add( node ); - allDescendants.add( node.getName() ); } public List getDescendants() { return descendants; } - public Set getAllDescendants() { - return allDescendants; - } - @Override public String toString() { return name; @@ -108,14 +87,11 @@ public boolean equals(Object obj) { return false; } Node other = (Node) obj; - if ( name == null ) { - if ( other.name != null ) { - return false; - } - } - else if ( !name.equals( other.name ) ) { + + if ( !Objects.equals( name, other.name ) ) { return false; } + return true; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/model/package-info.java index 6486be5670..06e132833c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

    diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.java new file mode 100644 index 0000000000..d71f67a2a4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.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.ap.internal.model.presence; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * A {@link PresenceCheck} that checks if all the given presence checks are present. + * + * @author Filip Hrisafov + */ +public class AllPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { + + private final Collection presenceChecks; + + public AllPresenceChecksPresenceCheck(Collection presenceChecks) { + this.presenceChecks = presenceChecks; + } + + public Collection getPresenceChecks() { + return presenceChecks; + } + + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>(); + for ( PresenceCheck presenceCheck : presenceChecks ) { + importTypes.addAll( presenceCheck.getImportTypes() ); + } + + return importTypes; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + AllPresenceChecksPresenceCheck that = (AllPresenceChecksPresenceCheck) o; + return Objects.equals( presenceChecks, that.presenceChecks ); + } + + @Override + public int hashCode() { + return Objects.hash( presenceChecks ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.java new file mode 100644 index 0000000000..5f8fac9623 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.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.ap.internal.model.presence; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * A {@link PresenceCheck} that checks if any of the given presence checks are present. + * + * @author Filip Hrisafov + */ +public class AnyPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck { + + private final Collection presenceChecks; + + public AnyPresenceChecksPresenceCheck(Collection presenceChecks) { + this.presenceChecks = presenceChecks; + } + + public Collection getPresenceChecks() { + return presenceChecks; + } + + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + + @Override + public Set getImportTypes() { + Set importTypes = new HashSet<>(); + for ( PresenceCheck presenceCheck : presenceChecks ) { + importTypes.addAll( presenceCheck.getImportTypes() ); + } + + return importTypes; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + AnyPresenceChecksPresenceCheck that = (AnyPresenceChecksPresenceCheck) o; + return Objects.equals( presenceChecks, that.presenceChecks ); + } + + @Override + public int hashCode() { + return Objects.hash( presenceChecks ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.java new file mode 100644 index 0000000000..0c7c132023 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.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.ap.internal.model.presence; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * A {@link PresenceCheck} that calls a Java expression. + * + * @author Filip Hrisafov + */ +public class JavaExpressionPresenceCheck extends ModelElement implements PresenceCheck { + + private final String javaExpression; + + public JavaExpressionPresenceCheck(String javaExpression) { + this.javaExpression = javaExpression; + } + + public String getJavaExpression() { + return javaExpression; + } + + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + JavaExpressionPresenceCheck that = (JavaExpressionPresenceCheck) o; + return Objects.equals( javaExpression, that.javaExpression ); + } + + @Override + public int hashCode() { + return Objects.hash( javaExpression ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java new file mode 100644 index 0000000000..2da4ae4023 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.java @@ -0,0 +1,70 @@ +/* + * 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.model.presence; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * A presence check that checks if the source reference is null. + * + * @author Filip Hrisafov + */ +public class NullPresenceCheck extends ModelElement implements PresenceCheck { + + private final String sourceReference; + private final boolean negate; + + public NullPresenceCheck(String sourceReference) { + this.sourceReference = sourceReference; + this.negate = false; + } + + public NullPresenceCheck(String sourceReference, boolean negate) { + this.sourceReference = sourceReference; + this.negate = negate; + } + + public String getSourceReference() { + return sourceReference; + } + + public boolean isNegate() { + return negate; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + @Override + public PresenceCheck negate() { + return new NullPresenceCheck( sourceReference, !negate ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + NullPresenceCheck that = (NullPresenceCheck) o; + return Objects.equals( sourceReference, that.sourceReference ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceReference ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java new file mode 100644 index 0000000000..d727c30f24 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.java @@ -0,0 +1,77 @@ +/* + * 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.model.presence; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.version.VersionInformation; + +/** + * Presence checker for {@link java.util.Optional} types. + * + * @author Ken Wang + */ +public class OptionalPresenceCheck extends ModelElement implements PresenceCheck { + + private final String sourceReference; + private final VersionInformation versionInformation; + private final boolean negate; + + public OptionalPresenceCheck(String sourceReference, VersionInformation versionInformation) { + this( sourceReference, versionInformation, false ); + } + + public OptionalPresenceCheck(String sourceReference, VersionInformation versionInformation, boolean negate) { + this.sourceReference = sourceReference; + this.versionInformation = versionInformation; + this.negate = negate; + } + + public String getSourceReference() { + return sourceReference; + } + + public VersionInformation getVersionInformation() { + return versionInformation; + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new OptionalPresenceCheck( sourceReference, versionInformation, !negate ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + OptionalPresenceCheck that = (OptionalPresenceCheck) o; + return Objects.equals( sourceReference, that.sourceReference ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceReference ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java new file mode 100644 index 0000000000..d35e89fce2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.java @@ -0,0 +1,77 @@ +/* + * 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.model.presence; + +import java.util.Collections; +import java.util.Objects; +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ModelElement; +import org.mapstruct.ap.internal.model.common.NegatePresenceCheck; +import org.mapstruct.ap.internal.model.common.PresenceCheck; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * A {@link PresenceCheck} that calls the suffix on the source reference. + * + * @author Filip Hrisafov + */ +public class SuffixPresenceCheck extends ModelElement implements PresenceCheck { + + private final String sourceReference; + private final String suffix; + private final boolean negate; + + public SuffixPresenceCheck(String sourceReference, String suffix) { + this( sourceReference, suffix, false ); + } + + public SuffixPresenceCheck(String sourceReference, String suffix, boolean negate) { + this.sourceReference = sourceReference; + this.suffix = suffix; + this.negate = negate; + } + + public String getSourceReference() { + return sourceReference; + } + + public String getSuffix() { + return suffix; + } + + public boolean isNegate() { + return negate; + } + + @Override + public PresenceCheck negate() { + return new NegatePresenceCheck( this ); + } + + @Override + public Set getImportTypes() { + return Collections.emptySet(); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SuffixPresenceCheck that = (SuffixPresenceCheck) o; + return Objects.equals( sourceReference, that.sourceReference ) + && Objects.equals( suffix, that.suffix ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceReference, suffix ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java deleted file mode 100644 index 7120404087..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.source; - -import java.util.List; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.prism.BeanMappingPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.Message; - - -/** - * Represents an bean mapping as configured via {@code @BeanMapping}. - * - * @author Sjaak Derksen - */ -public class BeanMapping { - - private final List qualifiers; - private final TypeMirror resultType; - private final NullValueMappingStrategyPrism nullValueMappingStrategy; - - public static BeanMapping fromPrism(BeanMappingPrism beanMapping, ExecutableElement method, - FormattingMessager messager) { - - if ( beanMapping == null ) { - return null; - } - - boolean resultTypeIsDefined = !TypeKind.VOID.equals( beanMapping.resultType().getKind() ); - - NullValueMappingStrategyPrism nullValueMappingStrategy = - null == beanMapping.values.nullValueMappingStrategy() - ? null - : NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() ); - - if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() - && ( nullValueMappingStrategy == null ) ) { - - messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS ); - } - - return new BeanMapping( - beanMapping.qualifiedBy(), - resultTypeIsDefined ? beanMapping.resultType() : null, - nullValueMappingStrategy - ); - } - - private BeanMapping(List qualifiers, TypeMirror mirror, NullValueMappingStrategyPrism nvms) { - - this.qualifiers = qualifiers; - this.resultType = mirror; - this.nullValueMappingStrategy = nvms; - } - - public List getQualifiers() { - return qualifiers; - } - - public TypeMirror getResultType() { - return resultType; - } - - public NullValueMappingStrategyPrism getNullValueMappingStrategy() { - return nullValueMappingStrategy; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java new file mode 100644 index 0000000000..73a4d7c12d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMappingOptions.java @@ -0,0 +1,253 @@ +/* + * 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.model.source; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.BeanMappingGem; +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.tools.gem.GemValue; + +/** + * Represents an bean mapping as configured via {@code @BeanMapping}. + * + * @author Sjaak Derksen + */ +public class BeanMappingOptions extends DelegatingOptions { + + private final SelectionParameters selectionParameters; + private final List ignoreUnmappedSourceProperties; + private final BeanMappingGem beanMapping; + + /** + * creates a mapping for inheritance. Will set + * + * @param beanMapping the bean mapping options that should be used + * @param isInverse whether the inheritance is inverse + * + * @return new mapping + */ + public static BeanMappingOptions forInheritance(BeanMappingOptions beanMapping, boolean isInverse) { + BeanMappingOptions options = new BeanMappingOptions( + SelectionParameters.forInheritance( beanMapping.selectionParameters ), + isInverse ? Collections.emptyList() : beanMapping.ignoreUnmappedSourceProperties, + beanMapping.beanMapping, + beanMapping + ); + return options; + } + + public static BeanMappingOptions forForgedMethods(BeanMappingOptions beanMapping) { + BeanMappingOptions options = new BeanMappingOptions( + beanMapping.selectionParameters != null ? + SelectionParameters.withoutResultType( beanMapping.selectionParameters ) : SelectionParameters.empty(), + Collections.emptyList(), + beanMapping.beanMapping, + beanMapping + ); + return options; + } + + public static BeanMappingOptions forSubclassForgedMethods(BeanMappingOptions beanMapping) { + + return new BeanMappingOptions( + beanMapping.selectionParameters != null ? + SelectionParameters.withoutResultType( beanMapping.selectionParameters ) : null, + beanMapping.ignoreUnmappedSourceProperties, + beanMapping.beanMapping, + beanMapping + ); + } + + public static BeanMappingOptions empty(DelegatingOptions delegatingOptions) { + return new BeanMappingOptions( SelectionParameters.empty(), Collections.emptyList(), null, delegatingOptions ); + } + + public static BeanMappingOptions getInstanceOn(BeanMappingGem beanMapping, MapperOptions mapperOptions, + ExecutableElement method, FormattingMessager messager, + TypeUtils typeUtils, TypeFactory typeFactory + ) { + if ( beanMapping == null || !isConsistent( beanMapping, method, messager ) ) { + return empty( mapperOptions ); + } + + Objects.requireNonNull( method ); + Objects.requireNonNull( messager ); + Objects.requireNonNull( method ); + Objects.requireNonNull( typeUtils ); + Objects.requireNonNull( typeFactory ); + + SelectionParameters selectionParameters = new SelectionParameters( + beanMapping.qualifiedBy().get(), + beanMapping.qualifiedByName().get(), + beanMapping.resultType().getValue(), + typeUtils + ); + + //TODO Do we want to add the reporting policy to the BeanMapping as well? To give more granular support? + BeanMappingOptions options = new BeanMappingOptions( + selectionParameters, + beanMapping.ignoreUnmappedSourceProperties().get(), + beanMapping, + mapperOptions + ); + return options; + } + + private static boolean isConsistent(BeanMappingGem gem, ExecutableElement method, + FormattingMessager messager) { + if ( !gem.resultType().hasValue() + && !gem.mappingControl().hasValue() + && !gem.qualifiedBy().hasValue() + && !gem.qualifiedByName().hasValue() + && !gem.ignoreUnmappedSourceProperties().hasValue() + && !gem.nullValueCheckStrategy().hasValue() + && !gem.nullValuePropertyMappingStrategy().hasValue() + && !gem.nullValueMappingStrategy().hasValue() + && !gem.subclassExhaustiveStrategy().hasValue() + && !gem.unmappedTargetPolicy().hasValue() + && !gem.unmappedSourcePolicy().hasValue() + && !gem.ignoreByDefault().hasValue() + && !gem.builder().hasValue() ) { + + messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS ); + return false; + } + return true; + } + + private BeanMappingOptions(SelectionParameters selectionParameters, + List ignoreUnmappedSourceProperties, + BeanMappingGem beanMapping, + DelegatingOptions next) { + super( next ); + this.selectionParameters = selectionParameters; + this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties; + this.beanMapping = beanMapping; + } + + // @Mapping, @BeanMapping + + @Override + public NullValueCheckStrategyGem getNullValueCheckStrategy() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::nullValueCheckStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( NullValueCheckStrategyGem::valueOf ) + .orElse( next().getNullValueCheckStrategy() ); + } + + @Override + public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::nullValuePropertyMappingStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( NullValuePropertyMappingStrategyGem::valueOf ) + .orElse( next().getNullValuePropertyMappingStrategy() ); + } + + @Override + public NullValueMappingStrategyGem getNullValueMappingStrategy() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::nullValueMappingStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( NullValueMappingStrategyGem::valueOf ) + .orElse( next().getNullValueMappingStrategy() ); + } + + @Override + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::subclassExhaustiveStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( SubclassExhaustiveStrategyGem::valueOf ) + .orElse( next().getSubclassExhaustiveStrategy() ); + } + + @Override + public TypeMirror getSubclassExhaustiveException() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::subclassExhaustiveException ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .orElse( next().getSubclassExhaustiveException() ); + } + + @Override + public ReportingPolicyGem unmappedTargetPolicy() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::unmappedTargetPolicy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( ReportingPolicyGem::valueOf ) + .orElse( next().unmappedTargetPolicy() ); + } + + @Override + public ReportingPolicyGem unmappedSourcePolicy() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::unmappedSourcePolicy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( ReportingPolicyGem::valueOf ) + .orElse( next().unmappedSourcePolicy() ); + } + + @Override + public BuilderGem getBuilder() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::builder ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .orElse( next().getBuilder() ); + } + + @Override + public MappingControl getMappingControl(ElementUtils elementUtils) { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::mappingControl ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( mc -> MappingControl.fromTypeMirror( mc, elementUtils ) ) + .orElse( next().getMappingControl( elementUtils ) ); + } + + // @BeanMapping specific + + public SelectionParameters getSelectionParameters() { + return selectionParameters; + } + + public boolean isIgnoredByDefault() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::ignoreByDefault ) + .map( GemValue::get ) + .orElse( false ); + } + + public List getIgnoreUnmappedSourceProperties() { + return ignoreUnmappedSourceProperties; + } + + public AnnotationMirror getMirror() { + return Optional.ofNullable( beanMapping ).map( BeanMappingGem::mirror ).orElse( null ); + } + + @Override + public boolean hasAnnotation() { + return beanMapping != null; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java new file mode 100644 index 0000000000..dfb0865ecc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.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.ap.internal.model.source; + +import java.util.Collection; +import java.util.Collections; + +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; + +/** + * Encapsulates all options specific for a condition check method. + * + * @author Filip Hrisafov + */ +public class ConditionMethodOptions { + + private static final ConditionMethodOptions EMPTY = new ConditionMethodOptions( Collections.emptyList() ); + + private final Collection conditionOptions; + + public ConditionMethodOptions(Collection conditionOptions) { + this.conditionOptions = conditionOptions; + } + + public boolean isStrategyApplicable(ConditionStrategyGem strategy) { + for ( ConditionOptions conditionOption : conditionOptions ) { + if ( conditionOption.getConditionStrategies().contains( strategy ) ) { + return true; + } + } + + return false; + } + + public boolean isAnyStrategyApplicable() { + return !conditionOptions.isEmpty(); + } + + public static ConditionMethodOptions empty() { + return EMPTY; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java new file mode 100644 index 0000000000..83e2bf2819 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java @@ -0,0 +1,172 @@ +/* + * 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.model.source; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.ConditionGem; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; + +/** + * Represents a condition configuration as configured via {@code @Condition}. + * + * @author Filip Hrisafov + */ +public class ConditionOptions { + + private final Set conditionStrategies; + + private ConditionOptions(Set conditionStrategies) { + this.conditionStrategies = conditionStrategies; + } + + public Collection getConditionStrategies() { + return conditionStrategies; + } + + public static ConditionOptions getInstanceOn(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + if ( condition == null ) { + return null; + } + + TypeMirror returnType = method.getReturnType(); + TypeKind returnTypeKind = returnType.getKind(); + // We only allow methods that return boolean or Boolean to be condition methods + if ( returnTypeKind != TypeKind.BOOLEAN ) { + if ( returnTypeKind != TypeKind.DECLARED ) { + return null; + } + DeclaredType declaredType = (DeclaredType) returnType; + TypeElement returnTypeElement = (TypeElement) declaredType.asElement(); + if ( !returnTypeElement.getQualifiedName().contentEquals( Boolean.class.getCanonicalName() ) ) { + return null; + } + } + + Set strategies = condition.appliesTo().get() + .stream() + .map( ConditionStrategyGem::valueOf ) + .collect( Collectors.toCollection( () -> EnumSet.noneOf( ConditionStrategyGem.class ) ) ); + + if ( strategies.isEmpty() ) { + messager.printMessage( + method, + condition.mirror(), + condition.appliesTo().getAnnotationValue(), + Message.CONDITION_MISSING_APPLIES_TO_STRATEGY + ); + + return null; + } + + boolean allStrategiesValid = true; + + for ( ConditionStrategyGem strategy : strategies ) { + boolean isStrategyValid = isValid( strategy, condition, method, parameters, messager ); + allStrategiesValid &= isStrategyValid; + } + + return allStrategiesValid ? new ConditionOptions( strategies ) : null; + } + + protected static boolean isValid(ConditionStrategyGem strategy, ConditionGem condition, + ExecutableElement method, List parameters, + FormattingMessager messager) { + if ( strategy == ConditionStrategyGem.SOURCE_PARAMETERS ) { + return hasValidStrategyForSourceProperties( condition, method, parameters, messager ); + } + else if ( strategy == ConditionStrategyGem.PROPERTIES ) { + return hasValidStrategyForProperties( condition, method, parameters, messager ); + } + else { + throw new IllegalStateException( "Invalid condition strategy: " + strategy ); + } + } + + protected static boolean hasValidStrategyForSourceProperties(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + for ( Parameter parameter : parameters ) { + if ( parameter.isSourceParameter() ) { + // source parameter is a valid parameter for a source condition check + continue; + } + + if ( parameter.isMappingContext() ) { + // mapping context parameter is a valid parameter for a source condition check + continue; + } + + messager.printMessage( + method, + condition.mirror(), + Message.CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER, + parameter.describe() + ); + return false; + } + return true; + } + + protected static boolean hasValidStrategyForProperties(ConditionGem condition, ExecutableElement method, + List parameters, + FormattingMessager messager) { + for ( Parameter parameter : parameters ) { + if ( parameter.isSourceParameter() ) { + // source parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isMappingContext() ) { + // mapping context parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isTargetType() ) { + // target type parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isMappingTarget() ) { + // mapping target parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isSourcePropertyName() ) { + // source property name parameter is a valid parameter for a property condition check + continue; + } + + if ( parameter.isTargetPropertyName() ) { + // target property name parameter is a valid parameter for a property condition check + continue; + } + + messager.printMessage( + method, + condition.mirror(), + Message.CONDITION_PROPERTIES_INVALID_PARAMETER, + parameter + ); + return false; + } + return true; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java new file mode 100644 index 0000000000..d958272f9b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -0,0 +1,186 @@ +/* + * 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.model.source; + +import java.util.Collections; +import java.util.Set; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; +import org.mapstruct.ap.internal.gem.MapperGem; +import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; +import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.util.ElementUtils; + +public class DefaultOptions extends DelegatingOptions { + + private final MapperGem mapper; + private final Options options; + + DefaultOptions(MapperGem mapper, Options options) { + super( null ); + this.mapper = mapper; + this.options = options; + } + + @Override + public String implementationName() { + return mapper.implementationName().getDefaultValue(); + } + + @Override + public String implementationPackage() { + return mapper.implementationPackage().getDefaultValue(); + } + + @Override + public Set uses() { + return Collections.emptySet(); + } + + @Override + public Set imports() { + return Collections.emptySet(); + } + + @Override + public ReportingPolicyGem unmappedTargetPolicy() { + ReportingPolicyGem unmappedTargetPolicy = options.getUnmappedTargetPolicy(); + if ( unmappedTargetPolicy != null ) { + return unmappedTargetPolicy; + } + return ReportingPolicyGem.valueOf( mapper.unmappedTargetPolicy().getDefaultValue() ); + } + + @Override + public ReportingPolicyGem unmappedSourcePolicy() { + ReportingPolicyGem unmappedSourcePolicy = options.getUnmappedSourcePolicy(); + if ( unmappedSourcePolicy != null ) { + return unmappedSourcePolicy; + } + return ReportingPolicyGem.valueOf( mapper.unmappedSourcePolicy().getDefaultValue() ); + } + + @Override + public ReportingPolicyGem typeConversionPolicy() { + return ReportingPolicyGem.valueOf( mapper.typeConversionPolicy().getDefaultValue() ); + } + + @Override + public String componentModel() { + String defaultComponentModel = options.getDefaultComponentModel(); + if ( defaultComponentModel != null ) { + return defaultComponentModel; + } + return mapper.componentModel().getDefaultValue(); + } + + public boolean suppressTimestampInGenerated() { + if ( mapper.suppressTimestampInGenerated().hasValue() ) { + return mapper.suppressTimestampInGenerated().getValue(); + } + return options.isSuppressGeneratorTimestamp(); + } + + @Override + public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { + return MappingInheritanceStrategyGem.valueOf( mapper.mappingInheritanceStrategy().getDefaultValue() ); + } + + @Override + public InjectionStrategyGem getInjectionStrategy() { + String defaultInjectionStrategy = options.getDefaultInjectionStrategy(); + if ( defaultInjectionStrategy != null ) { + return InjectionStrategyGem.valueOf( defaultInjectionStrategy.toUpperCase() ); + } + return InjectionStrategyGem.valueOf( mapper.injectionStrategy().getDefaultValue() ); + } + + @Override + public Boolean isDisableSubMappingMethodsGeneration() { + return mapper.disableSubMappingMethodsGeneration().getDefaultValue(); + } + + // BeanMapping and Mapping + + public CollectionMappingStrategyGem getCollectionMappingStrategy() { + return CollectionMappingStrategyGem.valueOf( mapper.collectionMappingStrategy().getDefaultValue() ); + } + + public NullValueCheckStrategyGem getNullValueCheckStrategy() { + return NullValueCheckStrategyGem.valueOf( mapper.nullValueCheckStrategy().getDefaultValue() ); + } + + public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy() { + return NullValuePropertyMappingStrategyGem.valueOf( + mapper.nullValuePropertyMappingStrategy().getDefaultValue() ); + } + + public NullValueMappingStrategyGem getNullValueMappingStrategy() { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMappingStrategy().getDefaultValue() ); + } + + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().getDefaultValue() ); + } + + public TypeMirror getSubclassExhaustiveException() { + return mapper.subclassExhaustiveException().getDefaultValue(); + } + + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + NullValueMappingStrategyGem nullValueIterableMappingStrategy = options.getNullValueIterableMappingStrategy(); + if ( nullValueIterableMappingStrategy != null ) { + return nullValueIterableMappingStrategy; + } + return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().getDefaultValue() ); + } + + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + NullValueMappingStrategyGem nullValueMapMappingStrategy = options.getNullValueMapMappingStrategy(); + if ( nullValueMapMappingStrategy != null ) { + return nullValueMapMappingStrategy; + } + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().getDefaultValue() ); + } + + @Override + public ClassAccessibilityGem accessibility() { + return ClassAccessibilityGem.valueOf( mapper.accessibility().getDefaultValue() ); + } + + public BuilderGem getBuilder() { + // TODO: I realized this is not correct, however it needs to be null in order to keep downward compatibility + // but assuming a default @Builder will make testcases fail. Not having a default means that you need to + // specify this mandatory on @MapperConfig and @Mapper. + return null; + } + + @Override + public MappingControl getMappingControl(ElementUtils elementUtils) { + return MappingControl.fromTypeMirror( mapper.mappingControl().getDefaultValue(), elementUtils ); + } + + @Override + public TypeMirror getUnexpectedValueMappingException() { + return null; + } + + @Override + public boolean hasAnnotation() { + return false; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java new file mode 100644 index 0000000000..e97f527745 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.java @@ -0,0 +1,161 @@ +/* + * 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.model.source; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; +import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; + +/** + * Chain Of Responsibility Pattern. + */ +public abstract class DelegatingOptions { + + private final DelegatingOptions next; + + public DelegatingOptions(DelegatingOptions next) { + this.next = next; + } + + // @Mapper and @MapperConfig + + public String implementationName() { + return next.implementationName(); + } + + public String implementationPackage() { + return next.implementationPackage(); + } + + public Set uses() { + return next.uses(); + } + + public Set imports() { + return next.imports(); + } + + public ReportingPolicyGem unmappedTargetPolicy() { + return next.unmappedTargetPolicy(); + } + + public ReportingPolicyGem unmappedSourcePolicy() { + return next.unmappedSourcePolicy(); + } + + public ReportingPolicyGem typeConversionPolicy() { + return next.typeConversionPolicy(); + } + + public String componentModel() { + return next.componentModel(); + } + + public boolean suppressTimestampInGenerated() { + return next.suppressTimestampInGenerated(); + } + + public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { + return next.getMappingInheritanceStrategy(); + } + + public InjectionStrategyGem getInjectionStrategy() { + return next.getInjectionStrategy(); + } + + public Boolean isDisableSubMappingMethodsGeneration() { + return next.isDisableSubMappingMethodsGeneration(); + } + + public ClassAccessibilityGem accessibility() { + return next.accessibility(); + } + + // BeanMapping and Mapping + + public CollectionMappingStrategyGem getCollectionMappingStrategy() { + return next.getCollectionMappingStrategy(); + } + + public NullValueCheckStrategyGem getNullValueCheckStrategy() { + return next.getNullValueCheckStrategy(); + } + + public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy() { + return next.getNullValuePropertyMappingStrategy(); + } + + public NullValueMappingStrategyGem getNullValueMappingStrategy() { + return next.getNullValueMappingStrategy(); + } + + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return next.getSubclassExhaustiveStrategy(); + } + + public TypeMirror getSubclassExhaustiveException() { + return next.getSubclassExhaustiveException(); + } + + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + return next.getNullValueIterableMappingStrategy(); + } + + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + return next.getNullValueMapMappingStrategy(); + } + + public BuilderGem getBuilder() { + return next.getBuilder(); + } + + public MappingControl getMappingControl(ElementUtils elementUtils) { + return next.getMappingControl( elementUtils ); + } + + public TypeMirror getUnexpectedValueMappingException() { + return next.getUnexpectedValueMappingException(); + } + + DelegatingOptions next() { + return next; + } + + protected Set toDeclaredTypes(List in, Set next) { + Set result = new LinkedHashSet<>(); + for ( TypeMirror typeMirror : in ) { + if ( typeMirror == null ) { + // When a class used in uses or imports is created by another annotation processor + // then javac will not return correct TypeMirror with TypeKind#ERROR, but rather a string "" + // the gem tools would return a null TypeMirror in that case. + // Therefore throw TypeHierarchyErroneousException so we can postpone the generation of the mapper + throw new TypeHierarchyErroneousException( typeMirror ); + } + + result.add( (DeclaredType) typeMirror ); + } + result.addAll( next ); + return result; + } + + public abstract boolean hasAnnotation(); + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMapping.java deleted file mode 100644 index 2e63337c92..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMapping.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.source; - -/** - * Represents the mapping between one enum constant and another. - * - * @author Gunnar Morling - */ -public class EnumMapping { - - private final String source; - private final String target; - - public EnumMapping(String source, String target) { - this.source = source; - this.target = target; - } - - /** - * @return the name of the constant in the source enum. - */ - public String getSource() { - return source; - } - - /** - * @return the name of the constant in the target enum. - */ - public String getTarget() { - return target; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java new file mode 100644 index 0000000000..41b74aa13a --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMappingOptions.java @@ -0,0 +1,149 @@ +/* + * 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.model.source; + +import java.util.Map; +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.EnumMappingGem; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.spi.EnumTransformationStrategy; + +import static org.mapstruct.ap.internal.util.Message.ENUMMAPPING_INCORRECT_TRANSFORMATION_STRATEGY; +import static org.mapstruct.ap.internal.util.Message.ENUMMAPPING_MISSING_CONFIGURATION; +import static org.mapstruct.ap.internal.util.Message.ENUMMAPPING_NO_ELEMENTS; + +/** + * Represents an enum mapping as configured via {@code @EnumMapping}. + * + * @author Filip Hrisafov + */ +public class EnumMappingOptions extends DelegatingOptions { + + private final EnumMappingGem enumMapping; + private final boolean inverse; + private final boolean valid; + + private EnumMappingOptions(EnumMappingGem enumMapping, boolean inverse, boolean valid, DelegatingOptions next) { + super( next ); + this.enumMapping = enumMapping; + this.inverse = inverse; + this.valid = valid; + } + + @Override + public boolean hasAnnotation() { + return enumMapping != null; + } + + public boolean isValid() { + return valid; + } + + public boolean hasNameTransformationStrategy() { + return hasAnnotation() && Strings.isNotEmpty( getNameTransformationStrategy() ); + } + + public String getNameTransformationStrategy() { + return enumMapping.nameTransformationStrategy().getValue(); + } + + public String getNameTransformationConfiguration() { + return enumMapping.configuration().getValue(); + } + + @Override + public TypeMirror getUnexpectedValueMappingException() { + if ( enumMapping != null && enumMapping.unexpectedValueMappingException().hasValue() ) { + return enumMapping.unexpectedValueMappingException().getValue(); + } + + return next().getUnexpectedValueMappingException(); + } + + public AnnotationMirror getMirror() { + return Optional.ofNullable( enumMapping ).map( EnumMappingGem::mirror ).orElse( null ); + } + + public boolean isInverse() { + return inverse; + } + + public EnumMappingOptions inverse() { + return new EnumMappingOptions( enumMapping, true, valid, next() ); + } + + public static EnumMappingOptions getInstanceOn(ExecutableElement method, MapperOptions mapperOptions, + Map enumTransformationStrategies, FormattingMessager messager) { + + EnumMappingGem enumMapping = EnumMappingGem.instanceOn( method ); + if ( enumMapping == null ) { + return new EnumMappingOptions( null, false, true, mapperOptions ); + } + else if ( !isConsistent( enumMapping, method, enumTransformationStrategies, messager ) ) { + return new EnumMappingOptions( null, false, false, mapperOptions ); + } + + return new EnumMappingOptions( + enumMapping, + false, + true, + mapperOptions + ); + } + + private static boolean isConsistent(EnumMappingGem gem, ExecutableElement method, + Map enumTransformationStrategies, FormattingMessager messager) { + + String strategy = gem.nameTransformationStrategy().getValue(); + String configuration = gem.configuration().getValue(); + + boolean isConsistent = false; + + if ( Strings.isNotEmpty( strategy ) || Strings.isNotEmpty( configuration ) ) { + if ( !enumTransformationStrategies.containsKey( strategy ) ) { + String registeredStrategies = Strings.join( enumTransformationStrategies.keySet(), ", " ); + messager.printMessage( + method, + gem.mirror(), + gem.nameTransformationStrategy().getAnnotationValue(), + ENUMMAPPING_INCORRECT_TRANSFORMATION_STRATEGY, + strategy, + registeredStrategies + ); + + return false; + } + else if ( Strings.isEmpty( configuration ) ) { + messager.printMessage( + method, + gem.mirror(), + gem.configuration().getAnnotationValue(), + ENUMMAPPING_MISSING_CONFIGURATION + ); + return false; + } + + isConsistent = true; + } + + isConsistent = isConsistent || gem.unexpectedValueMappingException().hasValue(); + + if ( !isConsistent ) { + messager.printMessage( + method, + gem.mirror(), + ENUMMAPPING_NO_ELEMENTS + ); + } + + return isConsistent; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java deleted file mode 100644 index d34b0afd54..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java +++ /dev/null @@ -1,218 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.source; - -import static org.mapstruct.ap.internal.util.Collections.first; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.lang.model.element.ExecutableElement; - -import org.mapstruct.ap.internal.model.common.Accessibility; -import org.mapstruct.ap.internal.model.common.Parameter; -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.util.MapperConfiguration; -import org.mapstruct.ap.internal.util.Strings; - -/** - * This method will be generated in absence of a suitable abstract method to implement. - * - * @author Sjaak Derksen - */ -public class ForgedMethod implements Method { - - private final List parameters; - private final Type returnType; - private final String name; - private final ExecutableElement positionHintElement; - private final List thrownTypes; - private final MapperConfiguration mapperConfiguration; - - /** - * Creates a new forged method with the given name. - * - * @param name the (unique name) for this method - * @param sourceType the source type - * @param targetType the target type. - * @param positionHintElement element used to for reference to the position in the source file. - */ - public ForgedMethod(String name, Type sourceType, Type targetType, MapperConfiguration mapperConfiguration, - ExecutableElement positionHintElement) { - String sourceParamName = Strings.decapitalize( sourceType.getName().replace( "[]", "" ) ); - String sourceParamSafeName = Strings.getSaveVariableName( sourceParamName ); - this.parameters = Arrays.asList( new Parameter( sourceParamSafeName, sourceType ) ); - this.returnType = targetType; - this.thrownTypes = new ArrayList(); - this.name = name; - this.mapperConfiguration = mapperConfiguration; - this.positionHintElement = positionHintElement; - } - - /** - * creates a new ForgedMethod with the same arguments but with a new name - * @param name the new name - * @param forgedMethod existing forge method - */ - public ForgedMethod(String name, ForgedMethod forgedMethod) { - this.parameters = forgedMethod.parameters; - this.returnType = forgedMethod.returnType; - this.thrownTypes = new ArrayList(); - this.mapperConfiguration = forgedMethod.mapperConfiguration; - this.positionHintElement = forgedMethod.positionHintElement; - this.name = name; - } - - @Override - public boolean matches(List sourceTypes, Type targetType) { - - if ( !targetType.equals( returnType ) ) { - return false; - } - - if ( parameters.size() != 1 || sourceTypes.size() != 1 ) { - return false; - } - - if ( !first( sourceTypes ).equals( parameters.get( 0 ).getType() ) ) { - return false; - } - - return true; - } - - @Override - public Type getDeclaringMapper() { - return null; - } - - @Override - public String getName() { - return name; - } - - @Override - public List getParameters() { - return parameters; - } - - @Override - public List getSourceParameters() { - return parameters; - } - - @Override - public Parameter getMappingTargetParameter() { - return null; - } - - @Override - public Parameter getTargetTypeParameter() { - return null; - } - - @Override - public Accessibility getAccessibility() { - return Accessibility.PROTECTED; - } - - @Override - public Type getReturnType() { - return returnType; - } - - @Override - public List getThrownTypes() { - return thrownTypes; - } - - public void addThrownTypes(List thrownTypesToAdd) { - for ( Type thrownType : thrownTypesToAdd ) { - // make sure there are no duplicates coming from the keyAssignment thrown types. - if ( !thrownTypes.contains( thrownType ) ) { - thrownTypes.add( thrownType ); - } - } - } - - @Override - public Type getResultType() { - return returnType; - } - - @Override - public List getParameterNames() { - List parameterNames = new ArrayList(); - for ( Parameter parameter : getParameters() ) { - parameterNames.add( parameter.getName() ); - } - return parameterNames; - } - - @Override - public boolean overridesMethod() { - return false; - } - - @Override - public ExecutableElement getExecutable() { - return positionHintElement; - } - - @Override - public boolean isLifecycleCallbackMethod() { - return false; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder( returnType.toString() ); - sb.append( " " ); - - sb.append( getName() ).append( "(" ).append( Strings.join( parameters, ", " ) ).append( ")" ); - - return sb.toString(); - } - - @Override - public boolean isStatic() { - return false; - } - - @Override - public boolean isDefault() { - return false; - } - - @Override - public Type getDefiningType() { - return null; - } - - @Override - public MapperConfiguration getMapperConfiguration() { - return mapperConfiguration; - } - - @Override - public boolean isUpdateMethod() { - return getMappingTargetParameter() != null; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java deleted file mode 100644 index 8359ba1cd5..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.source; - -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.prism.IterableMappingPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.Message; - -/** - * Represents an iterable mapping as configured via {@code @IterableMapping}. - * - * @author Gunnar Morling - */ -public class IterableMapping { - - private final String dateFormat; - private final List qualifiers; - private final TypeMirror qualifyingElementTargetType; - private final AnnotationMirror mirror; - private final AnnotationValue dateFormatAnnotationValue; - private final NullValueMappingStrategyPrism nullValueMappingStrategy; - - public static IterableMapping fromPrism(IterableMappingPrism iterableMapping, ExecutableElement method, - FormattingMessager messager) { - if ( iterableMapping == null ) { - return null; - } - - boolean elementTargetTypeIsDefined = !TypeKind.VOID.equals( iterableMapping.elementTargetType().getKind() ); - - NullValueMappingStrategyPrism nullValueMappingStrategy = - iterableMapping.values.nullValueMappingStrategy() == null - ? null - : NullValueMappingStrategyPrism.valueOf( iterableMapping.nullValueMappingStrategy() ); - - if ( !elementTargetTypeIsDefined - && iterableMapping.dateFormat().isEmpty() - && iterableMapping.qualifiedBy().isEmpty() - && ( nullValueMappingStrategy == null ) ) { - - messager.printMessage( method, Message.ITERABLEMAPPING_NO_ELEMENTS ); - } - - return new IterableMapping(iterableMapping.dateFormat(), - iterableMapping.qualifiedBy(), - elementTargetTypeIsDefined ? iterableMapping.elementTargetType() : null, - iterableMapping.mirror, - iterableMapping.values.dateFormat(), - nullValueMappingStrategy - ); - } - - private IterableMapping(String dateFormat, List qualifiers, TypeMirror resultType, - AnnotationMirror mirror, AnnotationValue dateFormatAnnotationValue, NullValueMappingStrategyPrism nvms) { - - this.dateFormat = dateFormat; - this.qualifiers = qualifiers; - this.qualifyingElementTargetType = resultType; - this.mirror = mirror; - this.dateFormatAnnotationValue = dateFormatAnnotationValue; - this.nullValueMappingStrategy = nvms; - } - - public String getDateFormat() { - return dateFormat; - } - - public List getQualifiers() { - return qualifiers; - } - - public TypeMirror getQualifyingElementTargetType() { - return qualifyingElementTargetType; - } - - public AnnotationMirror getMirror() { - return mirror; - } - - public AnnotationValue getDateFormatAnnotationValue() { - return dateFormatAnnotationValue; - } - - public NullValueMappingStrategyPrism getNullValueMappingStrategy() { - return nullValueMappingStrategy; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java new file mode 100644 index 0000000000..50fca3e4d2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMappingOptions.java @@ -0,0 +1,124 @@ +/* + * 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.model.source; + +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + +import org.mapstruct.ap.internal.gem.IterableMappingGem; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.tools.gem.GemValue; + +/** + * Represents an iterable mapping as configured via {@code @IterableMapping}. + * + * @author Gunnar Morling + */ +public class IterableMappingOptions extends DelegatingOptions { + + private final SelectionParameters selectionParameters; + private final FormattingParameters formattingParameters; + private final IterableMappingGem iterableMapping; + + public static IterableMappingOptions fromGem(IterableMappingGem iterableMapping, + MapperOptions mapperOptions, ExecutableElement method, + FormattingMessager messager, TypeUtils typeUtils) { + + if ( iterableMapping == null || !isConsistent( iterableMapping, method, messager ) ) { + IterableMappingOptions options = new IterableMappingOptions( + null, + SelectionParameters.empty(), + null, + mapperOptions + ); + return options; + } + + SelectionParameters selection = new SelectionParameters( + iterableMapping.qualifiedBy().get(), + iterableMapping.qualifiedByName().get(), + iterableMapping.elementTargetType().getValue(), + typeUtils + ); + + FormattingParameters formatting = new FormattingParameters( + iterableMapping.dateFormat().get(), + iterableMapping.numberFormat().get(), + iterableMapping.mirror(), + iterableMapping.dateFormat().getAnnotationValue(), + method, + iterableMapping.locale().getValue() + ); + + IterableMappingOptions options = + new IterableMappingOptions( formatting, selection, iterableMapping, mapperOptions ); + return options; + } + + private static boolean isConsistent(IterableMappingGem gem, ExecutableElement method, + FormattingMessager messager) { + if ( !gem.dateFormat().hasValue() + && !gem.numberFormat().hasValue() + && !gem.qualifiedBy().hasValue() + && !gem.qualifiedByName().hasValue() + && !gem.elementTargetType().hasValue() + && !gem.nullValueMappingStrategy().hasValue() ) { + messager.printMessage( method, Message.ITERABLEMAPPING_NO_ELEMENTS ); + return false; + } + return true; + } + + private IterableMappingOptions(FormattingParameters formattingParameters, SelectionParameters selectionParameters, + IterableMappingGem iterableMapping, + DelegatingOptions next) { + super( next ); + this.formattingParameters = formattingParameters; + this.selectionParameters = selectionParameters; + this.iterableMapping = iterableMapping; + } + + public SelectionParameters getSelectionParameters() { + return selectionParameters; + } + + public FormattingParameters getFormattingParameters() { + return formattingParameters; + } + + public AnnotationMirror getMirror() { + return Optional.ofNullable( iterableMapping ).map( IterableMappingGem::mirror ).orElse( null ); + } + + @Override + public NullValueMappingStrategyGem getNullValueMappingStrategy() { + return Optional.ofNullable( iterableMapping ).map( IterableMappingGem::nullValueMappingStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( NullValueMappingStrategyGem::valueOf ) + .orElse( next().getNullValueIterableMappingStrategy() ); + } + + public MappingControl getElementMappingControl(ElementUtils elementUtils) { + return Optional.ofNullable( iterableMapping ).map( IterableMappingGem::elementMappingControl ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( mc -> MappingControl.fromTypeMirror( mc, elementUtils ) ) + .orElse( next().getMappingControl( elementUtils ) ); + } + + @Override + public boolean hasAnnotation() { + return iterableMapping != null; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMapping.java deleted file mode 100644 index 12e0b1556b..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMapping.java +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.source; - -import java.util.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.prism.MapMappingPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.Message; - -/** - * Represents a map mapping as configured via {@code @MapMapping}. - * - * @author Gunnar Morling - */ -public class MapMapping { - - private final String keyFormat; - private final List keyQualifiers; - private final String valueFormat; - private final List valueQualifiers; - private final AnnotationMirror mirror; - private final TypeMirror keyQualifyingTargetType; - private final TypeMirror valueQualifyingTargetType; - private final NullValueMappingStrategyPrism nullValueMappingStrategy; - - public static MapMapping fromPrism(MapMappingPrism mapMapping, ExecutableElement method, - FormattingMessager messager) { - if ( mapMapping == null ) { - return null; - } - - NullValueMappingStrategyPrism nullValueMappingStrategy = - mapMapping.values.nullValueMappingStrategy() == null - ? null - : NullValueMappingStrategyPrism.valueOf( mapMapping.nullValueMappingStrategy() ); - - - boolean keyTargetTypeIsDefined = !TypeKind.VOID.equals( mapMapping.keyTargetType().getKind() ); - boolean valueTargetTypeIsDefined = !TypeKind.VOID.equals( mapMapping.valueTargetType().getKind() ); - if ( mapMapping.keyDateFormat().isEmpty() - && mapMapping.keyQualifiedBy().isEmpty() - && mapMapping.valueDateFormat().isEmpty() - && mapMapping.valueQualifiedBy().isEmpty() - && !keyTargetTypeIsDefined - && !valueTargetTypeIsDefined - && ( nullValueMappingStrategy == null ) ) { - - messager.printMessage( method, Message.MAPMAPPING_NO_ELEMENTS ); - } - - - return new MapMapping( - mapMapping.keyDateFormat(), - mapMapping.keyQualifiedBy(), - keyTargetTypeIsDefined ? mapMapping.keyTargetType() : null, - mapMapping.valueDateFormat(), - mapMapping.valueQualifiedBy(), - valueTargetTypeIsDefined ? mapMapping.valueTargetType() : null, - mapMapping.mirror, - nullValueMappingStrategy - ); - } - - private MapMapping(String keyFormat, List keyQualifiers, TypeMirror keyResultType, String valueFormat, - List valueQualifiers, TypeMirror valueResultType, AnnotationMirror mirror, - NullValueMappingStrategyPrism nvms ) { - this.keyFormat = keyFormat; - this.keyQualifiers = keyQualifiers; - this.keyQualifyingTargetType = keyResultType; - this.valueFormat = valueFormat; - this.valueQualifiers = valueQualifiers; - this.valueQualifyingTargetType = valueResultType; - this.mirror = mirror; - this.nullValueMappingStrategy = nvms; - } - - public String getKeyFormat() { - return keyFormat; - } - - public List getKeyQualifiers() { - return keyQualifiers; - } - - public TypeMirror getKeyQualifyingTargetType() { - return keyQualifyingTargetType; - } - - public String getValueFormat() { - return valueFormat; - } - - public List getValueQualifiers() { - return valueQualifiers; - } - - public TypeMirror getValueQualifyingTargetType() { - return valueQualifyingTargetType; - } - - public AnnotationMirror getMirror() { - return mirror; - } - - public NullValueMappingStrategyPrism getNullValueMappingStrategy() { - return nullValueMappingStrategy; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java new file mode 100644 index 0000000000..9f3d12faf3 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMappingOptions.java @@ -0,0 +1,175 @@ +/* + * 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.model.source; + +import java.util.Optional; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; + +import org.mapstruct.ap.internal.gem.MapMappingGem; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.tools.gem.GemValue; + +/** + * Represents a map mapping as configured via {@code @MapMapping}. + * + * @author Gunnar Morling + */ +public class MapMappingOptions extends DelegatingOptions { + + private final SelectionParameters keySelectionParameters; + private final SelectionParameters valueSelectionParameters; + private final FormattingParameters keyFormattingParameters; + private final FormattingParameters valueFormattingParameters; + private final MapMappingGem mapMapping; + + public static MapMappingOptions fromGem(MapMappingGem mapMapping, MapperOptions mapperOptions, + ExecutableElement method, FormattingMessager messager, + TypeUtils typeUtils) { + + if ( mapMapping == null || !isConsistent( mapMapping, method, messager ) ) { + MapMappingOptions options = new MapMappingOptions( + null, + SelectionParameters.empty(), + null, + SelectionParameters.empty(), + null, + mapperOptions + ); + return options; + } + + String locale = mapMapping.locale().getValue(); + + SelectionParameters keySelection = new SelectionParameters( + mapMapping.keyQualifiedBy().get(), + mapMapping.keyQualifiedByName().get(), + mapMapping.keyTargetType().getValue(), + typeUtils + ); + + SelectionParameters valueSelection = new SelectionParameters( + mapMapping.valueQualifiedBy().get(), + mapMapping.valueQualifiedByName().get(), + mapMapping.valueTargetType().getValue(), + typeUtils + ); + + FormattingParameters keyFormatting = new FormattingParameters( + mapMapping.keyDateFormat().get(), + mapMapping.keyNumberFormat().get(), + mapMapping.mirror(), + mapMapping.keyDateFormat().getAnnotationValue(), + method, + locale + ); + + FormattingParameters valueFormatting = new FormattingParameters( + mapMapping.valueDateFormat().get(), + mapMapping.valueNumberFormat().get(), + mapMapping.mirror(), + mapMapping.valueDateFormat().getAnnotationValue(), + method, + locale + ); + + MapMappingOptions options = new MapMappingOptions( + keyFormatting, + keySelection, + valueFormatting, + valueSelection, + mapMapping, + mapperOptions + ); + return options; + } + + private static boolean isConsistent(MapMappingGem gem, ExecutableElement method, + FormattingMessager messager) { + if ( !gem.keyDateFormat().hasValue() + && !gem.keyNumberFormat().hasValue() + && !gem.keyQualifiedBy().hasValue() + && !gem.keyQualifiedByName().hasValue() + && !gem.valueDateFormat().hasValue() + && !gem.valueNumberFormat().hasValue() + && !gem.valueQualifiedBy().hasValue() + && !gem.valueQualifiedByName().hasValue() + && !gem.keyTargetType().hasValue() + && !gem.valueTargetType().hasValue() + && !gem.nullValueMappingStrategy().hasValue() ) { + messager.printMessage( method, Message.MAPMAPPING_NO_ELEMENTS ); + return false; + } + return true; + } + + private MapMappingOptions(FormattingParameters keyFormatting, SelectionParameters keySelectionParameters, + FormattingParameters valueFormatting, SelectionParameters valueSelectionParameters, + MapMappingGem mapMapping, DelegatingOptions next ) { + super( next ); + this.keyFormattingParameters = keyFormatting; + this.keySelectionParameters = keySelectionParameters; + this.valueFormattingParameters = valueFormatting; + this.valueSelectionParameters = valueSelectionParameters; + this.mapMapping = mapMapping; + } + + public FormattingParameters getKeyFormattingParameters() { + return keyFormattingParameters; + } + + public SelectionParameters getKeySelectionParameters() { + return keySelectionParameters; + } + + public FormattingParameters getValueFormattingParameters() { + return valueFormattingParameters; + } + + public SelectionParameters getValueSelectionParameters() { + return valueSelectionParameters; + } + + public AnnotationMirror getMirror() { + return Optional.ofNullable( mapMapping ).map( MapMappingGem::mirror ).orElse( null ); + } + + @Override + public NullValueMappingStrategyGem getNullValueMappingStrategy() { + return Optional.ofNullable( mapMapping ).map( MapMappingGem::nullValueMappingStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( NullValueMappingStrategyGem::valueOf ) + .orElse( next().getNullValueMapMappingStrategy() ); + } + + public MappingControl getKeyMappingControl(ElementUtils elementUtils) { + return Optional.ofNullable( mapMapping ).map( MapMappingGem::keyMappingControl ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( mc -> MappingControl.fromTypeMirror( mc, elementUtils ) ) + .orElse( next().getMappingControl( elementUtils ) ); + } + + public MappingControl getValueMappingControl(ElementUtils elementUtils) { + return Optional.ofNullable( mapMapping ).map( MapMappingGem::valueMappingControl ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( mc -> MappingControl.fromTypeMirror( mc, elementUtils ) ) + .orElse( next().getMappingControl( elementUtils ) ); + } + + @Override + public boolean hasAnnotation() { + return mapMapping != null; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java new file mode 100644 index 0000000000..b51008b713 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -0,0 +1,204 @@ +/* + * 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.model.source; + +import java.util.Set; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; +import org.mapstruct.ap.internal.gem.MapperConfigGem; +import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; +import org.mapstruct.ap.internal.util.ElementUtils; + +public class MapperConfigOptions extends DelegatingOptions { + + private final MapperConfigGem mapperConfig; + + MapperConfigOptions(MapperConfigGem mapperConfig, DelegatingOptions next ) { + super( next ); + this.mapperConfig = mapperConfig; + } + + @Override + public String implementationName() { + return mapperConfig.implementationName().hasValue() ? mapperConfig.implementationName().get() : + next().implementationName(); + } + + @Override + public String implementationPackage() { + return mapperConfig.implementationPackage().hasValue() ? mapperConfig.implementationPackage().get() : + next().implementationPackage(); + } + + @Override + public Set uses() { + return toDeclaredTypes( mapperConfig.uses().get(), next().uses() ); + } + + @Override + public Set imports() { + return toDeclaredTypes( mapperConfig.imports().get(), next().imports() ); + } + + @Override + public ReportingPolicyGem unmappedTargetPolicy() { + return mapperConfig.unmappedTargetPolicy().hasValue() ? + ReportingPolicyGem.valueOf( mapperConfig.unmappedTargetPolicy().get() ) : next().unmappedTargetPolicy(); + + } + + @Override + public ReportingPolicyGem unmappedSourcePolicy() { + return mapperConfig.unmappedSourcePolicy().hasValue() ? + ReportingPolicyGem.valueOf( mapperConfig.unmappedSourcePolicy().get() ) : next().unmappedSourcePolicy(); + } + + @Override + public ReportingPolicyGem typeConversionPolicy() { + return mapperConfig.typeConversionPolicy().hasValue() ? + ReportingPolicyGem.valueOf( mapperConfig.typeConversionPolicy().get() ) : next().typeConversionPolicy(); + } + + @Override + public String componentModel() { + return mapperConfig.componentModel().hasValue() ? mapperConfig.componentModel().get() : next().componentModel(); + } + + @Override + public boolean suppressTimestampInGenerated() { + return mapperConfig.suppressTimestampInGenerated().hasValue() ? + mapperConfig.suppressTimestampInGenerated().get() : + next().suppressTimestampInGenerated(); + } + + @Override + public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { + return mapperConfig.mappingInheritanceStrategy().hasValue() ? + MappingInheritanceStrategyGem.valueOf( mapperConfig.mappingInheritanceStrategy().get() ) : + next().getMappingInheritanceStrategy(); + } + + @Override + public InjectionStrategyGem getInjectionStrategy() { + return mapperConfig.injectionStrategy().hasValue() ? + InjectionStrategyGem.valueOf( mapperConfig.injectionStrategy().get() ) : + next().getInjectionStrategy(); + } + + @Override + public Boolean isDisableSubMappingMethodsGeneration() { + return mapperConfig.disableSubMappingMethodsGeneration().hasValue() ? + mapperConfig.disableSubMappingMethodsGeneration().get() : + next().isDisableSubMappingMethodsGeneration(); + } + + // @Mapping, @BeanMapping + + @Override + public CollectionMappingStrategyGem getCollectionMappingStrategy() { + return mapperConfig.collectionMappingStrategy().hasValue() ? + CollectionMappingStrategyGem.valueOf( mapperConfig.collectionMappingStrategy().get() ) : + next().getCollectionMappingStrategy(); + } + + @Override + public NullValueCheckStrategyGem getNullValueCheckStrategy() { + return mapperConfig.nullValueCheckStrategy().hasValue() ? + NullValueCheckStrategyGem.valueOf( mapperConfig.nullValueCheckStrategy().get() ) : + next().getNullValueCheckStrategy(); + } + + @Override + public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy() { + return mapperConfig.nullValuePropertyMappingStrategy().hasValue() ? + NullValuePropertyMappingStrategyGem.valueOf( mapperConfig.nullValuePropertyMappingStrategy().get() ) : + next().getNullValuePropertyMappingStrategy(); + } + + @Override + public NullValueMappingStrategyGem getNullValueMappingStrategy() { + return mapperConfig.nullValueMappingStrategy().hasValue() ? + NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueMappingStrategy().get() ) : + next().getNullValueMappingStrategy(); + } + + @Override + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return mapperConfig.subclassExhaustiveStrategy().hasValue() ? + SubclassExhaustiveStrategyGem.valueOf( mapperConfig.subclassExhaustiveStrategy().get() ) : + next().getSubclassExhaustiveStrategy(); + } + + public TypeMirror getSubclassExhaustiveException() { + return mapperConfig.subclassExhaustiveException().hasValue() ? + mapperConfig.subclassExhaustiveException().get() : + next().getSubclassExhaustiveException(); + } + + @Override + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + if ( mapperConfig.nullValueIterableMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueIterableMappingStrategy().get() ); + } + if ( mapperConfig.nullValueMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueMappingStrategy().get() ); + } + return next().getNullValueIterableMappingStrategy(); + } + + @Override + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + if ( mapperConfig.nullValueMapMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueMapMappingStrategy().get() ); + } + if ( mapperConfig.nullValueMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapperConfig.nullValueMappingStrategy().get() ); + } + return next().getNullValueMapMappingStrategy(); + } + + @Override + public ClassAccessibilityGem accessibility() { + return mapperConfig.accessibility().hasValue() ? + ClassAccessibilityGem.valueOf( mapperConfig.accessibility().get() ) : + next().accessibility(); + } + + @Override + public BuilderGem getBuilder() { + return mapperConfig.builder().hasValue() ? mapperConfig.builder().get() : next().getBuilder(); + } + + @Override + public MappingControl getMappingControl(ElementUtils elementUtils) { + return mapperConfig.mappingControl().hasValue() ? + MappingControl.fromTypeMirror( mapperConfig.mappingControl().getValue(), elementUtils ) : + next().getMappingControl( elementUtils ); + } + + @Override + public TypeMirror getUnexpectedValueMappingException() { + return mapperConfig.unexpectedValueMappingException().hasValue() ? + mapperConfig.unexpectedValueMappingException().get() : + next().getUnexpectedValueMappingException(); + } + + @Override + public boolean hasAnnotation() { + return mapperConfig != null; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java new file mode 100644 index 0000000000..b30b8c95fa --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -0,0 +1,252 @@ +/* + * 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.model.source; + +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.BuilderGem; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; +import org.mapstruct.ap.internal.gem.MapperConfigGem; +import org.mapstruct.ap.internal.gem.MapperGem; +import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.gem.SubclassExhaustiveStrategyGem; +import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.util.ElementUtils; + +public class MapperOptions extends DelegatingOptions { + + private final MapperGem mapper; + private final DeclaredType mapperConfigType; + + public static MapperOptions getInstanceOn(TypeElement typeElement, Options options) { + MapperGem mapper = MapperGem.instanceOn( typeElement ); + MapperOptions mapperAnnotation; + DelegatingOptions defaults = new DefaultOptions( mapper, options ); + DeclaredType mapperConfigType; + if ( mapper.config().hasValue() && mapper.config().getValue().getKind() == TypeKind.DECLARED ) { + mapperConfigType = (DeclaredType) mapper.config().get(); + } + else { + mapperConfigType = null; + } + if ( mapperConfigType != null ) { + Element mapperConfigElement = mapperConfigType.asElement(); + MapperConfigGem mapperConfig = MapperConfigGem.instanceOn( mapperConfigElement ); + MapperConfigOptions mapperConfigAnnotation = new MapperConfigOptions( mapperConfig, defaults ); + mapperAnnotation = new MapperOptions( mapper, mapperConfigType, mapperConfigAnnotation ); + } + else { + mapperAnnotation = new MapperOptions( mapper, null, defaults ); + } + return mapperAnnotation; + } + + private MapperOptions(MapperGem mapper, DeclaredType mapperConfigType, DelegatingOptions next) { + super( next ); + this.mapper = mapper; + this.mapperConfigType = mapperConfigType; + } + + @Override + public String implementationName() { + return mapper.implementationName().hasValue() ? mapper.implementationName().get() : next().implementationName(); + } + + @Override + public String implementationPackage() { + return mapper.implementationPackage().hasValue() ? mapper.implementationPackage().get() : + next().implementationPackage(); + } + + @Override + public Set uses() { + return toDeclaredTypes( mapper.uses().get(), next().uses() ); + } + + @Override + public Set imports() { + return toDeclaredTypes( mapper.imports().get(), next().imports() ); + } + + @Override + public ReportingPolicyGem unmappedTargetPolicy() { + return mapper.unmappedTargetPolicy().hasValue() ? + ReportingPolicyGem.valueOf( mapper.unmappedTargetPolicy().get() ) : next().unmappedTargetPolicy(); + } + + @Override + public ReportingPolicyGem unmappedSourcePolicy() { + return mapper.unmappedSourcePolicy().hasValue() ? + ReportingPolicyGem.valueOf( mapper.unmappedSourcePolicy().get() ) : next().unmappedSourcePolicy(); + } + + @Override + public ReportingPolicyGem typeConversionPolicy() { + return mapper.typeConversionPolicy().hasValue() ? + ReportingPolicyGem.valueOf( mapper.typeConversionPolicy().get() ) : next().typeConversionPolicy(); + } + + @Override + public String componentModel() { + return mapper.componentModel().hasValue() ? mapper.componentModel().get() : next().componentModel(); + } + + @Override + public boolean suppressTimestampInGenerated() { + return mapper.suppressTimestampInGenerated().hasValue() ? + mapper.suppressTimestampInGenerated().get() : + next().suppressTimestampInGenerated(); + } + + @Override + public MappingInheritanceStrategyGem getMappingInheritanceStrategy() { + return mapper.mappingInheritanceStrategy().hasValue() ? + MappingInheritanceStrategyGem.valueOf( mapper.mappingInheritanceStrategy().get() ) : + next().getMappingInheritanceStrategy(); + } + + @Override + public InjectionStrategyGem getInjectionStrategy() { + return mapper.injectionStrategy().hasValue() ? + InjectionStrategyGem.valueOf( mapper.injectionStrategy().get() ) : + next().getInjectionStrategy(); + } + + @Override + public Boolean isDisableSubMappingMethodsGeneration() { + return mapper.disableSubMappingMethodsGeneration().hasValue() ? + mapper.disableSubMappingMethodsGeneration().get() : + next().isDisableSubMappingMethodsGeneration(); + } + + // @Mapping, @BeanMapping + + @Override + public CollectionMappingStrategyGem getCollectionMappingStrategy() { + return mapper.collectionMappingStrategy().hasValue() ? + CollectionMappingStrategyGem.valueOf( mapper.collectionMappingStrategy().get() ) : + next().getCollectionMappingStrategy(); + } + + @Override + public NullValueCheckStrategyGem getNullValueCheckStrategy() { + return mapper.nullValueCheckStrategy().hasValue() ? + NullValueCheckStrategyGem.valueOf( mapper.nullValueCheckStrategy().get() ) : + next().getNullValueCheckStrategy(); + } + + @Override + public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy() { + return mapper.nullValuePropertyMappingStrategy().hasValue() ? + NullValuePropertyMappingStrategyGem.valueOf( mapper.nullValuePropertyMappingStrategy().get() ) : + next().getNullValuePropertyMappingStrategy(); + } + + @Override + public NullValueMappingStrategyGem getNullValueMappingStrategy() { + return mapper.nullValueMappingStrategy().hasValue() ? + NullValueMappingStrategyGem.valueOf( mapper.nullValueMappingStrategy().get() ) : + next().getNullValueMappingStrategy(); + } + + @Override + public SubclassExhaustiveStrategyGem getSubclassExhaustiveStrategy() { + return mapper.subclassExhaustiveStrategy().hasValue() ? + SubclassExhaustiveStrategyGem.valueOf( mapper.subclassExhaustiveStrategy().get() ) : + next().getSubclassExhaustiveStrategy(); + } + + @Override + public ClassAccessibilityGem accessibility() { + return mapper.accessibility().hasValue() ? + ClassAccessibilityGem.valueOf( mapper.accessibility().get() ) : + next().accessibility(); + } + + @Override + public TypeMirror getSubclassExhaustiveException() { + return mapper.subclassExhaustiveException().hasValue() ? + mapper.subclassExhaustiveException().get() : + next().getSubclassExhaustiveException(); + } + + @Override + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + if ( mapper.nullValueIterableMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueIterableMappingStrategy().get() ); + } + if ( mapper.nullValueMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMappingStrategy().get() ); + } + return next().getNullValueIterableMappingStrategy(); + } + + @Override + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + if ( mapper.nullValueMapMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMapMappingStrategy().get() ); + } + if ( mapper.nullValueMappingStrategy().hasValue() ) { + return NullValueMappingStrategyGem.valueOf( mapper.nullValueMappingStrategy().get() ); + } + return next().getNullValueMapMappingStrategy(); + } + + @Override + public BuilderGem getBuilder() { + return mapper.builder().hasValue() ? mapper.builder().get() : next().getBuilder(); + } + + @Override + public MappingControl getMappingControl(ElementUtils elementUtils) { + return mapper.mappingControl().hasValue() ? + MappingControl.fromTypeMirror( mapper.mappingControl().getValue(), elementUtils ) : + next().getMappingControl( elementUtils ); + } + + @Override + public TypeMirror getUnexpectedValueMappingException() { + return mapper.unexpectedValueMappingException().hasValue() ? + mapper.unexpectedValueMappingException().get() : + next().getUnexpectedValueMappingException(); + } + + // @Mapper specific + + public DeclaredType mapperConfigType() { + return mapperConfigType; + } + + public boolean hasMapperConfig() { + return mapperConfigType != null; + } + + public boolean isValid() { + return mapper.isValid(); + } + + public AnnotationMirror getAnnotationMirror() { + return mapper.mirror(); + } + + @Override + public boolean hasAnnotation() { + return true; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java deleted file mode 100644 index 4d0144fa09..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java +++ /dev/null @@ -1,379 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.source; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ElementKind; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.MappingPrism; -import org.mapstruct.ap.internal.prism.MappingsPrism; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.Message; - -/** - * Represents a property mapping as configured via {@code @Mapping}. - * - * @author Gunnar Morling - */ -public class Mapping { - - private static final Pattern JAVA_EXPRESSION = Pattern.compile( "^java\\((.*)\\)$" ); - - private final String sourceName; - private final String constant; - private final String javaExpression; - private final String targetName; - private final String dateFormat; - private final String defaultValue; - private final List qualifiers; - private final TypeMirror resultType; - private final boolean isIgnored; - private final List dependsOn; - - private final AnnotationMirror mirror; - private final AnnotationValue sourceAnnotationValue; - private final AnnotationValue targetAnnotationValue; - private final AnnotationValue dependsOnAnnotationValue; - - private SourceReference sourceReference; - - public static Map> fromMappingsPrism(MappingsPrism mappingsAnnotation, - ExecutableElement method, - FormattingMessager messager) { - Map> mappings = new HashMap>(); - - for ( MappingPrism mappingPrism : mappingsAnnotation.value() ) { - Mapping mapping = fromMappingPrism( mappingPrism, method, messager ); - if ( mapping != null ) { - List mappingsOfProperty = mappings.get( mappingPrism.target() ); - if ( mappingsOfProperty == null ) { - mappingsOfProperty = new ArrayList(); - mappings.put( mappingPrism.target(), mappingsOfProperty ); - } - - mappingsOfProperty.add( mapping ); - - if ( mappingsOfProperty.size() > 1 && !isEnumType( method.getReturnType() ) ) { - messager.printMessage( method, Message.PROPERTYMAPPING_DUPLICATE_TARGETS, mappingPrism.target() ); - } - } - } - - return mappings; - } - - public static Mapping fromMappingPrism(MappingPrism mappingPrism, ExecutableElement element, - FormattingMessager messager) { - - - if ( mappingPrism.target().isEmpty() ) { - messager.printMessage( - element, - mappingPrism.mirror, - mappingPrism.values.target(), - Message.PROPERTYMAPPING_EMPTY_TARGET - ); - return null; - } - - if ( !mappingPrism.source().isEmpty() && mappingPrism.values.constant() != null ) { - messager.printMessage( element, Message.PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED ); - return null; - } - else if ( !mappingPrism.source().isEmpty() && mappingPrism.values.expression() != null ) { - messager.printMessage( element, Message.PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.expression() != null && mappingPrism.values.constant() != null ) { - messager.printMessage( element, Message.PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.expression() != null && mappingPrism.values.defaultValue() != null ) { - messager.printMessage( element, Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED ); - return null; - } - else if ( mappingPrism.values.constant() != null && mappingPrism.values.defaultValue() != null ) { - messager.printMessage( element, Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED ); - return null; - } - - String source = mappingPrism.source().isEmpty() ? null : mappingPrism.source(); - String constant = mappingPrism.values.constant() == null ? null : mappingPrism.constant(); - String expression = getExpression( mappingPrism, element, messager ); - String dateFormat = mappingPrism.values.dateFormat() == null ? null : mappingPrism.dateFormat(); - String defaultValue = mappingPrism.values.defaultValue() == null ? null : mappingPrism.defaultValue(); - - boolean resultTypeIsDefined = mappingPrism.values.resultType() != null; - TypeMirror resultType = resultTypeIsDefined ? mappingPrism.resultType() : null; - List dependsOn = - mappingPrism.dependsOn() != null ? mappingPrism.dependsOn() : Collections.emptyList(); - - return new Mapping( - source, - constant, - expression, - mappingPrism.target(), - dateFormat, - defaultValue, - mappingPrism.qualifiedBy(), - mappingPrism.ignore(), - mappingPrism.mirror, - mappingPrism.values.source(), - mappingPrism.values.target(), - mappingPrism.values.dependsOn(), - resultType, - dependsOn - ); - } - - @SuppressWarnings("checkstyle:parameternumber") - private Mapping(String sourceName, String constant, String javaExpression, String targetName, - String dateFormat, String defaultValue, List qualifiers, - boolean isIgnored, AnnotationMirror mirror, - AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue, - AnnotationValue dependsOnAnnotationValue, - TypeMirror resultType, List dependsOn) { - this.sourceName = sourceName; - this.constant = constant; - this.javaExpression = javaExpression; - this.targetName = targetName; - this.dateFormat = dateFormat; - this.defaultValue = defaultValue; - this.qualifiers = qualifiers; - this.isIgnored = isIgnored; - this.mirror = mirror; - this.sourceAnnotationValue = sourceAnnotationValue; - this.targetAnnotationValue = targetAnnotationValue; - this.dependsOnAnnotationValue = dependsOnAnnotationValue; - this.resultType = resultType; - this.dependsOn = dependsOn; - } - - private static String getExpression(MappingPrism mappingPrism, ExecutableElement element, - FormattingMessager messager) { - if ( mappingPrism.expression().isEmpty() ) { - return null; - } - - Matcher javaExpressionMatcher = JAVA_EXPRESSION.matcher( mappingPrism.expression() ); - - if ( !javaExpressionMatcher.matches() ) { - messager.printMessage( - element, mappingPrism.mirror, mappingPrism.values.expression(), - Message.PROPERTYMAPPING_INVALID_EXPRESSION - ); - return null; - } - - return javaExpressionMatcher.group( 1 ).trim(); - } - - private static boolean isEnumType(TypeMirror mirror) { - return mirror.getKind() == TypeKind.DECLARED && - ( (DeclaredType) mirror ).asElement().getKind() == ElementKind.ENUM; - } - - public void init(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory) { - - if ( !method.isEnumMapping() ) { - sourceReference = new SourceReference.BuilderFromMapping() - .mapping( this ) - .method( method ) - .messager( messager ) - .typeFactory( typeFactory ) - .build(); - } - } - - /** - * Returns the complete source name of this mapping, either a qualified (e.g. {@code parameter1.foo}) or - * unqualified (e.g. {@code foo}) property reference. - * - * @return The complete source name of this mapping. - */ - public String getSourceName() { - return sourceName; - } - - public String getConstant() { - return constant; - } - - public String getJavaExpression() { - return javaExpression; - } - - public String getTargetName() { - return targetName; - } - - public String getDateFormat() { - return dateFormat; - } - - public String getDefaultValue() { - return defaultValue; - } - - public List getQualifiers() { - return qualifiers; - } - - public boolean isIgnored() { - return isIgnored; - } - - public AnnotationMirror getMirror() { - return mirror; - } - - public AnnotationValue getSourceAnnotationValue() { - return sourceAnnotationValue; - } - - public AnnotationValue getTargetAnnotationValue() { - return targetAnnotationValue; - } - - public AnnotationValue getDependsOnAnnotationValue() { - return dependsOnAnnotationValue; - } - - public SourceReference getSourceReference() { - return sourceReference; - } - - public TypeMirror getResultType() { - return resultType; - } - - public List getDependsOn() { - return dependsOn; - } - - private boolean hasPropertyInReverseMethod(String name, SourceMethod method) { - CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy(); - return method.getResultType().getPropertyWriteAccessors( cms ).containsKey( name ); - } - - public Mapping reverse(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory) { - - // mapping can only be reversed if the source was not a constant nor an expression nor a nested property - if ( constant != null || javaExpression != null ) { - return null; - } - - // should only ignore a property when 1) there is a sourceName defined or 2) there's a name match - if ( isIgnored ) { - if ( sourceName == null && !hasPropertyInReverseMethod( targetName, method ) ) { - return null; - } - } - - // should not reverse a nested property - if ( sourceReference != null && sourceReference.getPropertyEntries().size() > 1 ) { - return null; - } - - // should generate error when parameter - if ( sourceReference != null && sourceReference.getPropertyEntries().isEmpty() ) { - // parameter mapping only, apparently the @InheritReverseConfiguration is intentional - // but erroneous. Lets raise an error to warn. - messager.printMessage( - method.getExecutable(), Message.PROPERTYMAPPING_REVERSAL_PROBLEM, - sourceReference.getParameter() - ); - return null; - } - - Mapping reverse = new Mapping( - sourceName != null ? targetName : null, - null, // constant - null, // expression - sourceName != null ? sourceName : targetName, - dateFormat, - null, - qualifiers, - isIgnored, - mirror, - sourceAnnotationValue, - targetAnnotationValue, - dependsOnAnnotationValue, - null, - Collections.emptyList() - ); - - reverse.init( method, messager, typeFactory ); - return reverse; - } - - /** - * Creates a copy of this mapping, which is adapted to the given method - * - * @param method the method to create the copy for - * @return the copy - */ - public Mapping copyForInheritanceTo(SourceMethod method) { - Mapping mapping = new Mapping( - sourceName, - constant, - javaExpression, - targetName, - dateFormat, - defaultValue, - qualifiers, - isIgnored, - mirror, - sourceAnnotationValue, - targetAnnotationValue, - dependsOnAnnotationValue, - resultType, - dependsOn - ); - - if ( sourceReference != null ) { - mapping.sourceReference = sourceReference.copyForInheritanceTo( method ); - } - - return mapping; - } - - @Override - public String toString() { - return "Mapping {" + - "\n sourceName='" + sourceName + "\'," + - "\n targetName='" + targetName + "\'," + - "\n}"; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingControl.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingControl.java new file mode 100644 index 0000000000..87448bfb65 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingControl.java @@ -0,0 +1,119 @@ +/* + * 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.model.source; + +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.MappingControlGem; +import org.mapstruct.ap.internal.gem.MappingControlUseGem; +import org.mapstruct.ap.internal.gem.MappingControlsGem; +import org.mapstruct.ap.internal.util.ElementUtils; + +public class MappingControl { + + private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation"; + private static final String ORG_MAPSTRUCT_PKG = "org.mapstruct"; + private static final String MAPPING_CONTROL_FQN = "org.mapstruct.control.MappingControl"; + private static final String MAPPING_CONTROLS_FQN = "org.mapstruct.control.MappingControls"; + + private boolean allowDirect = false; + private boolean allowTypeConversion = false; + private boolean allowMappingMethod = false; + private boolean allow2Steps = false; + + public static MappingControl fromTypeMirror(TypeMirror mirror, ElementUtils elementUtils) { + MappingControl mappingControl = new MappingControl(); + if ( TypeKind.DECLARED == mirror.getKind() ) { + resolveControls( mappingControl, ( (DeclaredType) mirror ).asElement(), new HashSet<>(), elementUtils ); + } + return mappingControl; + } + + private MappingControl() { + } + + public boolean allowDirect() { + return allowDirect; + } + + public boolean allowTypeConversion() { + return allowTypeConversion; + } + + public boolean allowMappingMethod() { + return allowMappingMethod; + } + + public boolean allowBy2Steps() { + return allow2Steps; + } + + private static void resolveControls(MappingControl control, Element element, Set handledElements, + ElementUtils elementUtils) { + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element lElement = annotationMirror.getAnnotationType().asElement(); + if ( isAnnotation( lElement, MAPPING_CONTROL_FQN ) ) { + determineMappingControl( control, MappingControlGem.instanceOn( element ) ); + } + else if ( isAnnotation( lElement, MAPPING_CONTROLS_FQN ) ) { + MappingControlsGem.instanceOn( element ) + .value() + .get() + .forEach( m -> determineMappingControl( control, m ) ); + } + else if ( !isAnnotationInPackage( lElement, JAVA_LANG_ANNOTATION_PGK, elementUtils ) + && !isAnnotationInPackage( lElement, ORG_MAPSTRUCT_PKG, elementUtils ) + && !handledElements.contains( lElement ) + ) { + // recur over annotation mirrors + handledElements.add( lElement ); + resolveControls( control, lElement, handledElements, elementUtils ); + } + } + } + + private static void determineMappingControl(MappingControl in, MappingControlGem gem) { + MappingControlUseGem use = MappingControlUseGem.valueOf( gem.value().get() ); + switch ( use ) { + case DIRECT: + in.allowDirect = true; + break; + case MAPPING_METHOD: + in.allowMappingMethod = true; + break; + case BUILT_IN_CONVERSION: + in.allowTypeConversion = true; + break; + case COMPLEX_MAPPING: + in.allow2Steps = true; + break; + default: + } + } + + private static boolean isAnnotationInPackage(Element element, String packageFQN, ElementUtils elementUtils) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return packageFQN.equals( elementUtils.getPackageOf( element ).getQualifiedName().toString() ); + } + return false; + } + + private static boolean isAnnotation(Element element, String annotationFQN) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return annotationFQN.equals( ( (TypeElement) element ).getQualifiedName().toString() ); + } + return false; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java new file mode 100644 index 0000000000..caf7639977 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -0,0 +1,413 @@ +/* + * 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.model.source; + +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; + +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.accessor.Accessor; + +import static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy; + +/** + * Encapsulates all options specifiable on a mapping method + * + * @author Andreas Gudian + */ +public class MappingMethodOptions { + private static final MappingMethodOptions EMPTY = new MappingMethodOptions( + null, + Collections.emptySet(), + null, + null, + null, + null, + Collections.emptyList(), + Collections.emptySet(), + null + ); + + private MapperOptions mapper; + private Set mappings; + private IterableMappingOptions iterableMapping; + private MapMappingOptions mapMapping; + private BeanMappingOptions beanMapping; + private EnumMappingOptions enumMappingOptions; + private List valueMappings; + private boolean fullyInitialized; + private Set subclassMappings; + + private SubclassValidator subclassValidator; + + public MappingMethodOptions(MapperOptions mapper, Set mappings, + IterableMappingOptions iterableMapping, + MapMappingOptions mapMapping, BeanMappingOptions beanMapping, + EnumMappingOptions enumMappingOptions, + List valueMappings, + Set subclassMappings, SubclassValidator subclassValidator) { + this.mapper = mapper; + this.mappings = mappings; + this.iterableMapping = iterableMapping; + this.mapMapping = mapMapping; + this.beanMapping = beanMapping; + this.enumMappingOptions = enumMappingOptions; + this.valueMappings = valueMappings; + this.subclassMappings = subclassMappings; + this.subclassValidator = subclassValidator; + } + + /** + * creates empty mapping options + * + * @return empty mapping options + */ + public static MappingMethodOptions empty() { + return EMPTY; + } + + /** + * @return the {@link MappingOptions}s configured for this method, keyed by target property name. Only for enum + * mapping methods a target will be mapped by several sources. + */ + public Set getMappings() { + return mappings; + } + + public IterableMappingOptions getIterableMapping() { + return iterableMapping; + } + + public MapMappingOptions getMapMapping() { + return mapMapping; + } + + public BeanMappingOptions getBeanMapping() { + return beanMapping; + } + + public EnumMappingOptions getEnumMappingOptions() { + return enumMappingOptions; + } + + public List getValueMappings() { + return valueMappings; + } + + public Set getSubclassMappings() { + return subclassMappings; + } + + public void setIterableMapping(IterableMappingOptions iterableMapping) { + this.iterableMapping = iterableMapping; + } + + public void setMapMapping(MapMappingOptions mapMapping) { + this.mapMapping = mapMapping; + } + + public void setBeanMapping(BeanMappingOptions beanMapping) { + this.beanMapping = beanMapping; + } + + public void setEnumMappingOptions(EnumMappingOptions enumMappingOptions) { + this.enumMappingOptions = enumMappingOptions; + } + + public void setValueMappings(List valueMappings) { + this.valueMappings = valueMappings; + } + + public MapperOptions getMapper() { + return mapper; + } + + /** + * @return the {@code true}, iff the options have been fully initialized by applying all available inheritance + * options + */ + public boolean isFullyInitialized() { + return fullyInitialized; + } + + public void markAsFullyInitialized() { + this.fullyInitialized = true; + } + + /** + * Merges in all the mapping options configured, giving the already defined options precedence. + * + * @param sourceMethod the method which inherits the options. + * @param templateMethod the template method with the options to inherit, may be {@code null} + * @param isInverse if {@code true}, the specified options are from an inverse method + * @param annotationMirror the annotation on which the compile errors will be shown. + */ + public void applyInheritedOptions(SourceMethod sourceMethod, SourceMethod templateMethod, boolean isInverse, + AnnotationMirror annotationMirror) { + MappingMethodOptions templateOptions = templateMethod.getOptions(); + if ( null != templateOptions ) { + if ( !getIterableMapping().hasAnnotation() && templateOptions.getIterableMapping().hasAnnotation() ) { + setIterableMapping( templateOptions.getIterableMapping() ); + } + + if ( !getMapMapping().hasAnnotation() && templateOptions.getMapMapping().hasAnnotation() ) { + setMapMapping( templateOptions.getMapMapping() ); + } + + if ( !getBeanMapping().hasAnnotation() && templateOptions.getBeanMapping().hasAnnotation() ) { + setBeanMapping( BeanMappingOptions.forInheritance( templateOptions.getBeanMapping( ), isInverse ) ); + } + + if ( !getEnumMappingOptions().hasAnnotation() && templateOptions.getEnumMappingOptions().hasAnnotation() ) { + EnumMappingOptions newEnumMappingOptions; + if ( isInverse ) { + newEnumMappingOptions = templateOptions.getEnumMappingOptions().inverse(); + } + else { + newEnumMappingOptions = templateOptions.getEnumMappingOptions(); + } + setEnumMappingOptions( newEnumMappingOptions ); + } + + if ( getValueMappings() == null ) { + if ( templateOptions.getValueMappings() != null ) { + // there were no mappings, so the inherited mappings are the new ones + setValueMappings( templateOptions.getValueMappings() ); + } + else { + setValueMappings( Collections.emptyList() ); + } + } + else { + if ( templateOptions.getValueMappings() != null ) { + // if there are also inherited mappings, we inverse and add them. + for ( ValueMappingOptions inheritedValueMapping : templateOptions.getValueMappings() ) { + ValueMappingOptions valueMapping = + isInverse ? inheritedValueMapping.inverse() : inheritedValueMapping; + if ( valueMapping != null + && !getValueMappings().contains( valueMapping ) ) { + getValueMappings().add( valueMapping ); + } + } + } + } + + if ( isInverse ) { + List inheritedMappings = SubclassMappingOptions.copyForInverseInheritance( + templateOptions.getSubclassMappings(), + getBeanMapping() ); + addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings ); + } + else if ( methodsHaveIdenticalSignature( templateMethod, sourceMethod ) ) { + List inheritedMappings = + SubclassMappingOptions + .copyForInheritance( + templateOptions.getSubclassMappings(), + getBeanMapping() ); + addAllNonRedefined( sourceMethod, annotationMirror, inheritedMappings ); + } + + Set newMappings = new LinkedHashSet<>(); + for ( MappingOptions mapping : templateOptions.getMappings() ) { + if ( isInverse ) { + if ( mapping.canInverse() ) { + newMappings.add( mapping.copyForInverseInheritance( templateMethod, getBeanMapping() ) ); + } + } + else { + newMappings.add( mapping.copyForForwardInheritance( templateMethod, getBeanMapping() ) ); + } + } + + // now add all (does not override duplicates and leaves original mappings) + addAllNonRedefined( newMappings ); + + // filter new mappings + filterNestedTargetIgnores( mappings ); + } + } + + private boolean methodsHaveIdenticalSignature(SourceMethod templateMethod, SourceMethod sourceMethod) { + return parametersAreOfIdenticalTypeAndOrder( templateMethod, sourceMethod ) + && resultTypeIsTheSame( templateMethod, sourceMethod ); + } + + private boolean parametersAreOfIdenticalTypeAndOrder(SourceMethod templateMethod, SourceMethod sourceMethod) { + if ( templateMethod.getParameters().size() != sourceMethod.getParameters().size() ) { + return false; + } + for ( int i = 0; i < templateMethod.getParameters().size(); i++ ) { + if ( !templateMethod.getParameters().get( i ).getType().equals( + sourceMethod.getParameters().get( i ).getType() ) ) { + return false; + } + } + return true; + } + + private boolean resultTypeIsTheSame(SourceMethod templateMethod, SourceMethod sourceMethod) { + return templateMethod.getResultType().equals( sourceMethod.getResultType() ); + } + + private void addAllNonRedefined(SourceMethod sourceMethod, AnnotationMirror annotationMirror, + List inheritedMappings) { + Set redefinedSubclassMappings = new HashSet<>( subclassMappings ); + for ( SubclassMappingOptions subclassMappingOption : inheritedMappings ) { + if ( !redefinedSubclassMappings.contains( subclassMappingOption ) ) { + if ( subclassValidator.isValidUsage( + sourceMethod.getExecutable(), + annotationMirror, + subclassMappingOption.getSource() ) ) { + subclassMappings.add( subclassMappingOption ); + } + } + } + } + + private void addAllNonRedefined(Set inheritedMappings) { + // We are only adding the targets here since this mappings have already been reversed + Set redefinedTargets = new HashSet<>(); + for ( MappingOptions redefinedMappings : mappings ) { + if ( redefinedMappings.getTargetName() != null ) { + redefinedTargets.add( redefinedMappings.getTargetName() ); + } + } + for ( MappingOptions inheritedMapping : inheritedMappings ) { + if ( inheritedMapping.isIgnored() + || !isRedefined( redefinedTargets, inheritedMapping.getTargetName() ) + ) { + mappings.add( inheritedMapping ); + } + } + } + + private boolean isRedefined(Set redefinedNames, String inheritedName ) { + if ( inheritedName != null ) { + for ( String redefinedName : redefinedNames ) { + if ( elementsAreContainedIn( inheritedName, redefinedName ) ) { + return true; + } + } + } + return false; + } + + private boolean elementsAreContainedIn( String redefinedName, String inheritedName ) { + if ( inheritedName != null && redefinedName.startsWith( inheritedName ) ) { + // it is possible to redefine an exact matching source name, because the same source can be mapped to + // multiple targets. It is not possible for target, but caught by the Set and equals method in + // MappingOptions. SourceName == null also could hint at redefinition + if ( redefinedName.length() > inheritedName.length() ) { + // redefined.lenght() > inherited.length(), first following character should be separator + return '.' == redefinedName.charAt( inheritedName.length() ); + } + } + return false; + } + + public void applyIgnoreAll(SourceMethod method, TypeFactory typeFactory, + FormattingMessager messager) { + CollectionMappingStrategyGem cms = method.getOptions().getMapper().getCollectionMappingStrategy(); + Type writeType = method.getResultType(); + if ( !method.isUpdateMethod() ) { + writeType = typeFactory.effectiveResultTypeFor( + writeType, + method.getOptions().getBeanMapping().getBuilder() + ); + } + Map writeAccessors = writeType.getPropertyWriteAccessors( cms ); + + + for ( MappingOptions mapping : mappings ) { + String mappedTargetProperty = getFirstTargetPropertyName( mapping ); + if ( !".".equals( mappedTargetProperty ) ) { + // Remove the mapped target property from the write accessors + writeAccessors.remove( mappedTargetProperty ); + } + else { + messager.printMessage( + method.getExecutable(), + getBeanMapping().getMirror(), + Message.BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS + ); + // Nothing more to do if this is reached + return; + } + } + + // The writeAccessors now contains only the accessors that should be ignored + for ( String targetPropertyName : writeAccessors.keySet() ) { + MappingOptions mapping = MappingOptions.forIgnore( targetPropertyName ); + mappings.add( mapping ); + } + } + + private void filterNestedTargetIgnores( Set mappings) { + + // collect all properties to ignore, and safe their target name ( == same name as first ref target property) + Set ignored = getMappingTargetNamesBy( MappingOptions::isIgnored, mappings ); + mappings.removeIf( m -> isToBeIgnored( ignored, m ) ); + } + + private boolean isToBeIgnored(Set ignored, MappingOptions mapping) { + String[] propertyEntries = getPropertyEntries( mapping ); + return propertyEntries.length > 1 && ignored.contains( propertyEntries[ 0 ] ); + } + + private String[] getPropertyEntries( MappingOptions mapping ) { + return mapping.getTargetName().split( "\\." ); + } + + private String getFirstTargetPropertyName(MappingOptions mapping) { + String targetName = mapping.getTargetName(); + if ( ".".equals( targetName ) ) { + return targetName; + } + + return getPropertyEntries( mapping )[0]; + } + + /** + * SubclassMappingOptions are not inherited to forged methods. They would result in an infinite loop if they were. + * + * @return a MappingMethodOptions without SubclassMappingOptions or SubclassValidator. + */ + public static MappingMethodOptions getForgedMethodInheritedOptions(MappingMethodOptions options) { + return new MappingMethodOptions( + options.mapper, + options.mappings, + options.iterableMapping, + options.mapMapping, + BeanMappingOptions.forForgedMethods( options.beanMapping ), + options.enumMappingOptions, + options.valueMappings, + Collections.emptySet(), + null ); + } + + public static MappingMethodOptions getSubclassForgedMethodInheritedOptions(MappingMethodOptions options) { + return new MappingMethodOptions( + options.mapper, + options.mappings, + options.iterableMapping, + options.mapMapping, + BeanMappingOptions.forSubclassForgedMethods( options.beanMapping ), + options.enumMappingOptions, + options.valueMappings, + Collections.emptySet(), + null ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.java new file mode 100644 index 0000000000..7d0ab3a7ec --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodUtils.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.model.source; + +import org.mapstruct.ap.internal.model.common.Type; + +import static org.mapstruct.ap.internal.util.Collections.first; + +/** + * Utility class for mapping methods. + * + * @author Filip Hrisafov + */ +public final class MappingMethodUtils { + + /** + * Hide default constructor. + */ + private MappingMethodUtils() { + } + + /** + * Checks if the provided {@code method} is for enum mapping. A Method is an Enum Mapping method when the + *

      + *
    1. source parameter type and result type are enum types
    2. + *
    3. source parameter type is a String and result type is an enum type
    4. + *
    5. source parameter type is a enum type and result type is a String
    6. + *
    + * + * @param method to check + * + * @return {@code true} if the method is for enum mapping, {@code false} otherwise + */ + public static boolean isEnumMapping(Method method) { + if ( method.getSourceParameters().size() != 1 ) { + return false; + } + + Type source = first( method.getSourceParameters() ).getType(); + Type result = method.getResultType(); + if ( source.isEnumType() && result.isEnumType() ) { + return true; + } + if ( source.isString() && result.isEnumType() ) { + return true; + } + if ( source.isEnumType() && result.isString() ) { + return true; + } + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java index 8a1d6e5fc4..746aca5a32 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingOptions.java @@ -1,154 +1,586 @@ -/** - * Copyright 2012-2015 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.model.source; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.gem.MappingGem; +import org.mapstruct.ap.internal.gem.MappingsGem; +import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem; +import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.tools.gem.GemValue; /** - * Encapsulates all options specifiable on a mapping method + * Represents a property mapping as configured via {@code @Mapping} (no intermediate state). * - * @author Andreas Gudian + * @author Gunnar Morling */ -public class MappingOptions { - private Map> mappings; - private IterableMapping iterableMapping; - private MapMapping mapMapping; - private BeanMapping beanMapping; - private boolean fullyInitialized; +public class MappingOptions extends DelegatingOptions { + + private static final Pattern JAVA_EXPRESSION = Pattern.compile( "^\\s*java\\((.*)\\)\\s*$", Pattern.DOTALL ); + + private final String sourceName; + private final String constant; + private final String javaExpression; + private final String defaultJavaExpression; + private final String conditionJavaExpression; + private final String targetName; + private final String defaultValue; + private final FormattingParameters formattingParameters; + private final SelectionParameters selectionParameters; + + private final boolean isIgnored; + private final Set dependsOn; + + private final Element element; + private final AnnotationValue sourceAnnotationValue; + private final AnnotationValue targetAnnotationValue; + private final MappingGem mapping; + + private final InheritContext inheritContext; + + public static class InheritContext { - public MappingOptions(Map> mappings, IterableMapping iterableMapping, MapMapping mapMapping, - BeanMapping beanMapping) { - this.mappings = mappings; - this.iterableMapping = iterableMapping; - this.mapMapping = mapMapping; - this.beanMapping = beanMapping; + private final boolean isReversed; + private final boolean isForwarded; + private final Method templateMethod; + + public InheritContext(boolean isReversed, boolean isForwarded, Method templateMethod) { + this.isReversed = isReversed; + this.isForwarded = isForwarded; + this.templateMethod = templateMethod; + } + + public boolean isReversed() { + return isReversed; + } + + public boolean isForwarded() { + return isForwarded; + } + + public Method getTemplateMethod() { + return templateMethod; + } + } + + public static Set getMappingTargetNamesBy(Predicate predicate, + Set mappings) { + return mappings.stream() + .filter( predicate ) + .map( MappingOptions::getTargetName ) + .collect( Collectors.toCollection( LinkedHashSet::new ) ); + } + + public static void addInstances(MappingsGem gem, ExecutableElement method, + BeanMappingOptions beanMappingOptions, + FormattingMessager messager, TypeUtils typeUtils, + Set mappings) { + + for ( MappingGem mapping : gem.value().getValue() ) { + addInstance( mapping, method, beanMappingOptions, messager, typeUtils, mappings ); + } + } + + public static void addInstance(MappingGem mapping, ExecutableElement method, + BeanMappingOptions beanMappingOptions, FormattingMessager messager, + TypeUtils typeUtils, + Set mappings) { + + if ( !isConsistent( mapping, method, messager ) ) { + return; + } + + String source = mapping.source().getValue(); + String constant = mapping.constant().getValue(); + String expression = getExpression( mapping, method, messager ); + String defaultExpression = getDefaultExpression( mapping, method, messager ); + String conditionExpression = getConditionExpression( mapping, method, messager ); + String dateFormat = mapping.dateFormat().getValue(); + String numberFormat = mapping.numberFormat().getValue(); + String locale = mapping.locale().getValue(); + + String defaultValue = mapping.defaultValue().getValue(); + + Set dependsOn = mapping.dependsOn().hasValue() ? + new LinkedHashSet<>( mapping.dependsOn().getValue() ) : + Collections.emptySet(); + + FormattingParameters formattingParam = new FormattingParameters( + dateFormat, + numberFormat, + mapping.mirror(), + mapping.dateFormat().getAnnotationValue(), + method, + locale + ); + SelectionParameters selectionParams = new SelectionParameters( + mapping.qualifiedBy().get(), + mapping.qualifiedByName().get(), + mapping.conditionQualifiedBy().get(), + mapping.conditionQualifiedByName().get(), + mapping.resultType().getValue(), + typeUtils + ); + + MappingOptions options = new MappingOptions( + mapping.target().getValue(), + method, + mapping.target().getAnnotationValue(), + source, + mapping.source().getAnnotationValue(), + constant, + expression, + defaultExpression, + conditionExpression, + defaultValue, + mapping.ignore().get(), + formattingParam, + selectionParams, + dependsOn, + mapping, + null, + beanMappingOptions + ); + + if ( mappings.contains( options ) ) { + messager.printMessage( method, Message.PROPERTYMAPPING_DUPLICATE_TARGETS, mapping.target().get() ); + } + else { + mappings.add( options ); + } + } + + public static MappingOptions forIgnore(String targetName) { + return new MappingOptions( + targetName, + null, + null, + null, + null, + null, + null, + null, + null, + null, + true, + null, + SelectionParameters.empty(), + Collections.emptySet(), + null, + null, + null + ); + } + + private static boolean isConsistent(MappingGem gem, ExecutableElement method, + FormattingMessager messager) { + + if ( !gem.target().hasValue() ) { + messager.printMessage( + method, + gem.mirror(), + gem.target().getAnnotationValue(), + Message.PROPERTYMAPPING_EMPTY_TARGET + ); + return false; + } + + Message message = null; + if ( gem.source().hasValue() && gem.constant().hasValue() ) { + message = Message.PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED; + } + else if ( gem.expression().hasValue() && gem.conditionQualifiedByName().hasValue() ) { + message = Message.PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED; + } + else if ( gem.source().hasValue() && gem.expression().hasValue() ) { + message = Message.PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED; + } + else if ( gem.expression().hasValue() && gem.constant().hasValue() ) { + message = Message.PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED; + } + else if ( gem.expression().hasValue() && gem.defaultValue().hasValue() ) { + message = Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED; + } + else if ( gem.constant().hasValue() && gem.defaultValue().hasValue() ) { + message = Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED; + } + else if ( gem.expression().hasValue() && gem.defaultExpression().hasValue() ) { + message = Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_EXPRESSION_BOTH_DEFINED; + } + else if ( gem.expression().hasValue() && gem.conditionExpression().hasValue() ) { + message = Message.PROPERTYMAPPING_EXPRESSION_AND_CONDITION_EXPRESSION_BOTH_DEFINED; + } + else if ( gem.constant().hasValue() && gem.defaultExpression().hasValue() ) { + message = Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_EXPRESSION_BOTH_DEFINED; + } + else if ( gem.constant().hasValue() && gem.conditionExpression().hasValue() ) { + message = Message.PROPERTYMAPPING_CONSTANT_AND_CONDITION_EXPRESSION_BOTH_DEFINED; + } + else if ( gem.defaultValue().hasValue() && gem.defaultExpression().hasValue() ) { + message = Message.PROPERTYMAPPING_DEFAULT_VALUE_AND_DEFAULT_EXPRESSION_BOTH_DEFINED; + } + else if ( gem.expression().hasValue() + && ( gem.qualifiedByName().hasValue() || gem.qualifiedBy().hasValue() ) ) { + message = Message.PROPERTYMAPPING_EXPRESSION_AND_QUALIFIER_BOTH_DEFINED; + } + else if ( gem.nullValuePropertyMappingStrategy().hasValue() && gem.defaultValue().hasValue() ) { + message = Message.PROPERTYMAPPING_DEFAULT_VALUE_AND_NVPMS; + } + else if ( gem.nullValuePropertyMappingStrategy().hasValue() && gem.constant().hasValue() ) { + message = Message.PROPERTYMAPPING_CONSTANT_VALUE_AND_NVPMS; + } + else if ( gem.nullValuePropertyMappingStrategy().hasValue() && gem.expression().hasValue() ) { + message = Message.PROPERTYMAPPING_EXPRESSION_VALUE_AND_NVPMS; + } + else if ( gem.nullValuePropertyMappingStrategy().hasValue() && gem.defaultExpression().hasValue() ) { + message = Message.PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS; + } + else if ( gem.nullValuePropertyMappingStrategy().hasValue() + && gem.ignore().hasValue() && gem.ignore().getValue() ) { + message = Message.PROPERTYMAPPING_IGNORE_AND_NVPMS; + } + else if ( ".".equals( gem.target().get() ) && gem.ignore().hasValue() && gem.ignore().getValue() ) { + message = Message.PROPERTYMAPPING_TARGET_THIS_AND_IGNORE; + } + else if ( ".".equals( gem.target().get() ) && !gem.source().hasValue() ) { + message = Message.PROPERTYMAPPING_TARGET_THIS_NO_SOURCE; + } + + if ( message == null ) { + return true; + } + else { + messager.printMessage( method, gem.mirror(), message ); + return false; + } + } + + @SuppressWarnings("checkstyle:parameternumber") + private MappingOptions(String targetName, + Element element, + AnnotationValue targetAnnotationValue, + String sourceName, + AnnotationValue sourceAnnotationValue, + String constant, + String javaExpression, + String defaultJavaExpression, + String conditionJavaExpression, + String defaultValue, + boolean isIgnored, + FormattingParameters formattingParameters, + SelectionParameters selectionParameters, + Set dependsOn, + MappingGem mapping, + InheritContext inheritContext, + DelegatingOptions next + ) { + super( next ); + this.targetName = targetName; + this.element = element; + this.targetAnnotationValue = targetAnnotationValue; + this.sourceName = sourceName; + this.sourceAnnotationValue = sourceAnnotationValue; + this.constant = constant; + this.javaExpression = javaExpression; + this.defaultJavaExpression = defaultJavaExpression; + this.conditionJavaExpression = conditionJavaExpression; + this.defaultValue = defaultValue; + this.isIgnored = isIgnored; + this.formattingParameters = formattingParameters; + this.selectionParameters = selectionParameters; + this.dependsOn = dependsOn; + this.mapping = mapping; + this.inheritContext = inheritContext; + } + + private static String getExpression(MappingGem mapping, ExecutableElement element, + FormattingMessager messager) { + if ( !mapping.expression().hasValue() ) { + return null; + } + + Matcher javaExpressionMatcher = JAVA_EXPRESSION.matcher( mapping.expression().get() ); + + if ( !javaExpressionMatcher.matches() ) { + messager.printMessage( + element, + mapping.mirror(), + mapping.expression().getAnnotationValue(), + Message.PROPERTYMAPPING_INVALID_EXPRESSION + ); + return null; + } + + return javaExpressionMatcher.group( 1 ).trim(); + } + + private static String getDefaultExpression(MappingGem mapping, ExecutableElement element, + FormattingMessager messager) { + if ( !mapping.defaultExpression().hasValue() ) { + return null; + } + + Matcher javaExpressionMatcher = JAVA_EXPRESSION.matcher( mapping.defaultExpression().get() ); + + if ( !javaExpressionMatcher.matches() ) { + messager.printMessage( + element, + mapping.mirror(), + mapping.defaultExpression().getAnnotationValue(), + Message.PROPERTYMAPPING_INVALID_DEFAULT_EXPRESSION + ); + return null; + } + + return javaExpressionMatcher.group( 1 ).trim(); + } + + private static String getConditionExpression(MappingGem mapping, ExecutableElement element, + FormattingMessager messager) { + if ( !mapping.conditionExpression().hasValue() ) { + return null; + } + + Matcher javaExpressionMatcher = JAVA_EXPRESSION.matcher( mapping.conditionExpression().get() ); + + if ( !javaExpressionMatcher.matches() ) { + messager.printMessage( + element, + mapping.mirror(), + mapping.conditionExpression().getAnnotationValue(), + Message.PROPERTYMAPPING_INVALID_CONDITION_EXPRESSION + ); + return null; + } + + return javaExpressionMatcher.group( 1 ).trim(); + } + + public String getTargetName() { + return targetName; + } + + public AnnotationValue getTargetAnnotationValue() { + return targetAnnotationValue; } /** - * @return the {@link Mapping}s configured for this method, keyed by target property name. Only for enum mapping - * methods a target will be mapped by several sources. + * Returns the complete source name of this mapping, either a qualified (e.g. {@code parameter1.foo}) or + * unqualified (e.g. {@code foo}) property reference. + * + * @return The complete source name of this mapping. */ - public Map> getMappings() { - return mappings; + public String getSourceName() { + return sourceName; + } + + public AnnotationValue getSourceAnnotationValue() { + return sourceAnnotationValue; + } + + public String getConstant() { + return constant; + } + + public String getJavaExpression() { + return javaExpression; + } + + public String getDefaultJavaExpression() { + return defaultJavaExpression; + } + + public String getConditionJavaExpression() { + return conditionJavaExpression; + } + + public String getDefaultValue() { + return defaultValue; + } + + public FormattingParameters getFormattingParameters() { + return formattingParameters; + } + + public SelectionParameters getSelectionParameters() { + return selectionParameters; + } + + public boolean isIgnored() { + return isIgnored; + } + + public AnnotationMirror getMirror() { + return Optional.ofNullable( mapping ).map( MappingGem::mirror ).orElse( null ); } - public IterableMapping getIterableMapping() { - return iterableMapping; + public Element getElement() { + return element; } - public MapMapping getMapMapping() { - return mapMapping; + public AnnotationValue getDependsOnAnnotationValue() { + return Optional.ofNullable( mapping ) + .map( MappingGem::dependsOn ) + .map( GemValue::getAnnotationValue ) + .orElse( null ); } - public BeanMapping getBeanMapping() { - return beanMapping; + public Set getDependsOn() { + return dependsOn; } - public void setMappings(Map> mappings) { - this.mappings = mappings; + public InheritContext getInheritContext() { + return inheritContext; } - public void setIterableMapping(IterableMapping iterableMapping) { - this.iterableMapping = iterableMapping; + @Override + public NullValueCheckStrategyGem getNullValueCheckStrategy() { + return Optional.ofNullable( mapping ).map( MappingGem::nullValueCheckStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( NullValueCheckStrategyGem::valueOf ) + .orElse( next().getNullValueCheckStrategy() ); } - public void setMapMapping(MapMapping mapMapping) { - this.mapMapping = mapMapping; + @Override + public NullValuePropertyMappingStrategyGem getNullValuePropertyMappingStrategy() { + return Optional.ofNullable( mapping ).map( MappingGem::nullValuePropertyMappingStrategy ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( NullValuePropertyMappingStrategyGem::valueOf ) + .orElse( next().getNullValuePropertyMappingStrategy() ); } - public void setBeanMapping(BeanMapping beanMapping) { - this.beanMapping = beanMapping; + @Override + public MappingControl getMappingControl(ElementUtils elementUtils) { + return Optional.ofNullable( mapping ).map( MappingGem::mappingControl ) + .filter( GemValue::hasValue ) + .map( GemValue::getValue ) + .map( mc -> MappingControl.fromTypeMirror( mc, elementUtils ) ) + .orElse( next().getMappingControl( elementUtils ) ); } /** - * @return the {@code true}, iff the options have been fully initialized by applying all available inheritance - * options + * Mapping can only be inversed if the source was not a constant nor an expression + * + * @return true when the above applies */ - public boolean isFullyInitialized() { - return fullyInitialized; + public boolean canInverse() { + return constant == null && javaExpression == null; } - public void markAsFullyInitialized() { - this.fullyInitialized = true; + public MappingOptions copyForInverseInheritance(SourceMethod templateMethod, + BeanMappingOptions beanMappingOptions ) { + + MappingOptions mappingOptions = new MappingOptions( + sourceName != null ? sourceName : targetName, + templateMethod.getExecutable(), + targetAnnotationValue, + sourceName != null ? targetName : null, + sourceAnnotationValue, + null, // constant + null, // expression + null, // defaultExpression + null, // conditionExpression + null, + isIgnored, + formattingParameters, + selectionParameters, + Collections.emptySet(), + mapping, + new InheritContext( true, false, templateMethod ), + beanMappingOptions + ); + return mappingOptions; + } /** - * Merges in all the mapping options configured, giving the already defined options precedence. + * Creates a copy of this mapping * - * @param inherited the options to inherit, may be {@code null} - * @param isInverse if {@code true}, the specified options are from an inverse method - * @param method the source method - * @param messager the messager - * @param typeFactory the type factory + * @param templateMethod the template method for the inheritance + * @param beanMappingOptions the bean mapping options + * + * @return the copy */ - public void applyInheritedOptions(MappingOptions inherited, boolean isInverse, SourceMethod method, - FormattingMessager messager, TypeFactory typeFactory) { - if ( null != inherited ) { - if ( getIterableMapping() == null ) { - if ( inherited.getIterableMapping() != null ) { - setIterableMapping( inherited.getIterableMapping() ); - } - } - - if ( getMapMapping() == null ) { - if ( inherited.getMapMapping() != null ) { - setMapMapping( inherited.getMapMapping() ); - } - } - - if ( getBeanMapping() == null ) { - if ( inherited.getBeanMapping() != null ) { - setBeanMapping( inherited.getBeanMapping() ); - } - } - - Map> newMappings = new HashMap>(); - - for ( List lmappings : inherited.getMappings().values() ) { - for ( Mapping mapping : lmappings ) { - if ( isInverse ) { - mapping = mapping.reverse( method, messager, typeFactory ); - } - - if ( mapping != null ) { - List mappingsOfProperty = newMappings.get( mapping.getTargetName() ); - if ( mappingsOfProperty == null ) { - mappingsOfProperty = new ArrayList(); - newMappings.put( mapping.getTargetName(), mappingsOfProperty ); - } - - mappingsOfProperty.add( mapping.copyForInheritanceTo( method ) ); - } - } - } - - // now add all of its own mappings - newMappings.putAll( getMappings() ); - setMappings( newMappings ); - } - - markAsFullyInitialized(); + public MappingOptions copyForForwardInheritance(SourceMethod templateMethod, + BeanMappingOptions beanMappingOptions ) { + MappingOptions mappingOptions = new MappingOptions( + targetName, + templateMethod.getExecutable(), + targetAnnotationValue, + sourceName, + sourceAnnotationValue, + constant, + javaExpression, + defaultJavaExpression, + conditionJavaExpression, + defaultValue, + isIgnored, + formattingParameters, + selectionParameters, + dependsOn, + mapping, + new InheritContext( false, true, templateMethod ), + beanMappingOptions + ); + return mappingOptions; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( ".".equals( this.targetName ) ) { + // target this will never be equal to any other target this or any other. + return false; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + MappingOptions mapping = (MappingOptions) o; + return targetName.equals( mapping.targetName ); } + + @Override + public int hashCode() { + return Objects.hash( targetName ); + } + + @Override + public String toString() { + return "Mapping {" + + "\n sourceName='" + sourceName + "\'," + + "\n targetName='" + targetName + "\'," + + "\n}"; + } + + @Override + public boolean hasAnnotation() { + return mapping != null; + } + } + diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index 9dc13c632b..d9c9ce41fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -1,32 +1,17 @@ -/** - * Copyright 2012-2015 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.model.source; import java.util.List; - import javax.lang.model.element.ExecutableElement; import org.mapstruct.ap.internal.model.common.Accessibility; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; -import org.mapstruct.ap.internal.util.MapperConfiguration; /** * This interface makes available common method properties and a matching method There are 2 known implementors: @@ -70,20 +55,41 @@ public interface Method { List getParameters(); /** - * returns the list of 'true' source parameters excluding the parameter(s) that is designated as - * target by means of the target annotation {@link #getMappingTargetParameter() }. + * returns the list of 'true' source parameters excluding the parameter(s) that are designated as target, target + * type or context parameter. * * @return list of 'true' source parameters */ List getSourceParameters(); /** - * Returns the parameter designated as mapping target (if present) {@link org.mapstruct.MappingTarget } + * returns the list of mapping context parameters, i.e. those parameters that are annotated with + * {@link org.mapstruct.Context}. + * + * @return list of context parameters + */ + List getContextParameters(); + + /** + * @return a mapping between {@link #getContextParameters()} to factory and lifecycle methods provided by them. + */ + ParameterProvidedMethods getContextProvidedMethods(); + + /** + * Returns the parameter designated as mapping target (if present) {@link org.mapstruct.MappingTarget} * * @return mapping target parameter (when present) null otherwise. */ Parameter getMappingTargetParameter(); + /** + * Returns whether the method is designated as bean factory for + * mapping target {@link org.mapstruct.ObjectFactory } + * + * @return true if it is a target bean factory. + */ + boolean isObjectFactory(); + /** * Returns the parameter designated as target type (if present) {@link org.mapstruct.TargetType } * @@ -91,7 +97,6 @@ public interface Method { */ Parameter getTargetTypeParameter(); - /** * Returns the {@link Accessibility} of this method. * @@ -121,7 +126,6 @@ public interface Method { */ Type getResultType(); - /** * * @return the names of the parameters of this mapping method @@ -157,12 +161,6 @@ public interface Method { */ Type getDefiningType(); - /** - * - * @return the mapper config when this method needs to be implemented - */ - MapperConfiguration getMapperConfiguration(); - /** * @return {@code true}, if the method represents a mapping lifecycle callback (Before/After mapping method) */ @@ -173,4 +171,33 @@ public interface Method { * {@code @MappingTarget}. */ boolean isUpdateMethod(); + + /** + * + * @return the mapping options for this method + */ + MappingMethodOptions getOptions(); + + default ConditionMethodOptions getConditionOptions() { + return ConditionMethodOptions.empty(); + } + + /** + * @return the first source type, intended for mapping methods from single source to target + */ + default Type getMappingSourceType() { + return getSourceParameters().get( 0 ).getType(); + } + + /** + * @return the short name for error messages when verbose, full name when not + */ + String describe(); + + /** + * Returns the formal type parameters of this method in declaration order. + * + * @return the formal type parameters, or an empty list if there are none + */ + List getTypeParameters(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index 607d6f5a45..c62012472c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -1,45 +1,22 @@ -/** - * Copyright 2012-2015 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.model.source; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; - -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.ArrayType; +import java.util.stream.Collectors; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.WildcardType; -import javax.lang.model.util.SimpleTypeVisitor6; -import javax.lang.model.util.Types; 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 static org.mapstruct.ap.internal.util.Collections.hasNonNullElements; -import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.isSubType; +import org.mapstruct.ap.internal.util.TypeUtils; /** * SourceMethodMatcher $8.4 of the JavaLanguage specification describes a method body as such: @@ -69,10 +46,10 @@ public class MethodMatcher { private final SourceMethod candidateMethod; - private final Types typeUtils; + private final TypeUtils typeUtils; private final TypeFactory typeFactory; - MethodMatcher(Types typeUtils, TypeFactory typeFactory, SourceMethod candidateMethod) { + MethodMatcher(TypeUtils typeUtils, TypeFactory typeFactory, SourceMethod candidateMethod) { this.typeUtils = typeUtils; this.candidateMethod = candidateMethod; this.typeFactory = typeFactory; @@ -82,336 +59,363 @@ public class MethodMatcher { * Whether the given source and target types are matched by this matcher's candidate method. * * @param sourceTypes the source types - * @param resultType the target type + * @param targetType the target type * @return {@code true} when both, source type and target types match the signature of this matcher's method; * {@code false} otherwise. */ - boolean matches(List sourceTypes, Type resultType) { - - // check & collect generic types. - Map genericTypesMap = new HashMap(); - - if ( hasNonNullElements( sourceTypes ) ) { - // if sourceTypes contains non-null elements then only methods with all source parameters matching qualify - if ( candidateMethod.getSourceParameters().size() == sourceTypes.size() ) { - int i = 0; - for ( Parameter candidateSourceParam : candidateMethod.getSourceParameters() ) { - Type sourceType = sourceTypes.get( i++ ); - if ( sourceType == null - || !matchSourceType( sourceType, candidateSourceParam.getType(), genericTypesMap ) ) { - return false; - } - } - } - else { - return false; - } - } - else { - // if the sourceTypes empty/contains only nulls then only methods with zero source parameters qualify - if ( !candidateMethod.getSourceParameters().isEmpty() ) { - return false; - } - } - - // check if the method matches the proper result type to construct - Parameter targetTypeParameter = candidateMethod.getTargetTypeParameter(); - if ( targetTypeParameter != null ) { - Type returnClassType = typeFactory.classTypeOf( resultType ); - if ( !matchSourceType( returnClassType, targetTypeParameter.getType(), genericTypesMap ) ) { - return false; - } - } + boolean matches(List sourceTypes, Type targetType) { - // check result type - if ( !matchResultType( resultType, genericTypesMap ) ) { + GenericAnalyser analyser = + new GenericAnalyser( typeFactory, typeUtils, candidateMethod, sourceTypes, targetType ); + if ( !analyser.lineUp() ) { return false; } - // check if all type parameters are indeed mapped - if ( candidateMethod.getExecutable().getTypeParameters().size() != genericTypesMap.size() ) { - return false; + for ( int i = 0; i < sourceTypes.size(); i++ ) { + Type candidateSourceParType = analyser.candidateParTypes.get( i ); + if ( !sourceTypes.get( i ).isAssignableTo( candidateSourceParType ) + || isPrimitiveToObject( sourceTypes.get( i ), candidateSourceParType ) ) { + return false; + } } - - // check if all entries are in the bounds - for ( Map.Entry entry : genericTypesMap.entrySet() ) { - if ( !isWithinBounds( entry.getValue(), getTypeParamFromCandidate( entry.getKey() ) ) ) { - // checks if the found Type is in bounds of the TypeParameters bounds. + // TODO: TargetType checking should not be part of method selection, it should be in checking the annotation + // (the relation target / target type, target type being a class) + + if ( !analyser.candidateReturnType.isVoid() ) { + if ( targetType.isPrimitive() ) { + // If the target type is primitive + // then we are going to check if its boxed equivalent + // is assignable to the candidate return type + // This is done because primitives can be assigned from their own narrower counterparts + // directly without any casting. + // e.g. a Long is assignable to a primitive double + // However, in order not to lose information we are not going to allow this + return targetType.getBoxedEquivalent() + .isAssignableTo( analyser.candidateReturnType.getBoxedEquivalent() ); + } + if ( !( analyser.candidateReturnType.isAssignableTo( targetType ) ) ) { return false; } } + return true; } - private boolean matchSourceType(Type sourceType, - Type candidateSourceType, - Map genericTypesMap) { - - if ( !isJavaLangObject( candidateSourceType.getTypeMirror() ) ) { - TypeMatcher parameterMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_FROM, genericTypesMap ); - if ( !parameterMatcher.visit( candidateSourceType.getTypeMirror(), sourceType.getTypeMirror() ) ) { - if ( sourceType.isPrimitive() ) { - // the candidate source is primitive, so promote to its boxed type and check again (autobox) - TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) sourceType.getTypeMirror() ).asType(); - if ( !parameterMatcher.visit( candidateSourceType.getTypeMirror(), boxedType ) ) { - return false; - } - } - else { - // NOTE: unboxing is deliberately not considered here. This should be handled via type-conversion - // (for NPE safety). - return false; - } - } + /** + * Primitive to Object (Boxed Type) should be handled by a type conversion rather than a direct mapping + * Direct mapping runs the risk of null pointer exceptions: e.g. in the assignment of Integer to int, Integer + * can be null. + * + * @param type the type to be assigned + * @param isAssignableTo the type to which @param type should be assignable to + * @return true if isAssignable is a primitive type and type is an object + */ + private boolean isPrimitiveToObject( Type type, Type isAssignableTo ) { + if ( isAssignableTo.isPrimitive() ) { + return !type.isPrimitive(); } - return true; + return false; } - private boolean matchResultType(Type resultType, Map genericTypesMap) { + private static class GenericAnalyser { + + private TypeFactory typeFactory; + private TypeUtils typeUtils; + private Method candidateMethod; + private List sourceTypes; + private Type targetType; + + GenericAnalyser(TypeFactory typeFactory, TypeUtils typeUtils, Method candidateMethod, + List sourceTypes, Type targetType) { + this.typeFactory = typeFactory; + this.typeUtils = typeUtils; + this.candidateMethod = candidateMethod; + this.sourceTypes = sourceTypes; + this.targetType = targetType; + } - Type candidateResultType = candidateMethod.getResultType(); + Type candidateReturnType = null; + List candidateParTypes; - if ( !isJavaLangObject( candidateResultType.getTypeMirror() ) && !candidateResultType.isVoid() ) { + private boolean lineUp() { - final Assignability visitedAssignability; - if ( candidateMethod.getReturnType().isVoid() ) { - // for void-methods, the result-type of the candidate needs to be assignable from the given result type - visitedAssignability = Assignability.VISITED_ASSIGNABLE_FROM; - } - else { - // for non-void methods, the result-type of the candidate needs to be assignable to the given result - // type - visitedAssignability = Assignability.VISITED_ASSIGNABLE_TO; + if ( candidateMethod.getParameters().size() != sourceTypes.size() ) { + return false; } - TypeMatcher returnTypeMatcher = new TypeMatcher( visitedAssignability, genericTypesMap ); - if ( !returnTypeMatcher.visit( candidateResultType.getTypeMirror(), resultType.getTypeMirror() ) ) { - if ( resultType.isPrimitive() ) { - TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) resultType.getTypeMirror() ).asType(); - TypeMatcher boxedReturnTypeMatcher = - new TypeMatcher( visitedAssignability, genericTypesMap ); + if ( !candidateMethod.getTypeParameters().isEmpty() ) { - if ( !boxedReturnTypeMatcher.visit( candidateResultType.getTypeMirror(), boxedType ) ) { - return false; + this.candidateParTypes = new ArrayList<>(); + + // Per generic method parameter the associated type variable candidates + Map methodParCandidates = new HashMap<>(); + + // Get candidates + boolean success = getCandidates( methodParCandidates ); + if ( !success ) { + return false; + } + + // Check type bounds + boolean withinBounds = candidatesWithinBounds( methodParCandidates ); + if ( !withinBounds ) { + return false; + } + + // Represent result as map. + Map resolvedPairs = new HashMap<>(); + for ( TypeVarCandidate candidate : methodParCandidates.values() ) { + for ( Type.ResolvedPair pair : candidate.pairs ) { + resolvedPairs.put( pair.getParameter(), pair.getMatch() ); } } - else if ( candidateResultType.getTypeMirror().getKind().isPrimitive() ) { - TypeMirror boxedCandidateReturnType = - typeUtils.boxedClass( (PrimitiveType) candidateResultType.getTypeMirror() ).asType(); - TypeMatcher boxedReturnTypeMatcher = - new TypeMatcher( visitedAssignability, genericTypesMap ); - if ( !boxedReturnTypeMatcher.visit( boxedCandidateReturnType, resultType.getTypeMirror() ) ) { + // Resolve parameters and return type by using the found candidates + int nrOfMethodPars = candidateMethod.getParameters().size(); + for ( int i = 0; i < nrOfMethodPars; i++ ) { + Type candidateType = resolve( candidateMethod.getParameters().get( i ).getType(), resolvedPairs ); + if ( candidateType == null ) { return false; } + this.candidateParTypes.add( candidateType ); } + if ( !candidateMethod.getReturnType().isVoid() ) { + this.candidateReturnType = resolve( candidateMethod.getReturnType(), resolvedPairs ); + if ( this.candidateReturnType == null ) { + return false; + } + } else { + this.candidateReturnType = candidateMethod.getReturnType(); + } + } + else { + this.candidateParTypes = candidateMethod.getParameters().stream() + .map( Parameter::getType ) + .collect( Collectors.toList() ); + this.candidateReturnType = candidateMethod.getReturnType(); + } + return true; + } + + /** + * {@code T map( U in ) } + * + * Resolves all method generic parameter candidates + * + * @param methodParCandidates Map, keyed by the method generic parameter (T, U extends Number), with their + * respective candidates + * + * @return false no match or conflict has been found * + */ + boolean getCandidates( Map methodParCandidates) { + + int nrOfMethodPars = candidateMethod.getParameters().size(); + Type returnType = candidateMethod.getReturnType(); + + for ( int i = 0; i < nrOfMethodPars; i++ ) { + Type sourceType = sourceTypes.get( i ); + Parameter par = candidateMethod.getParameters().get( i ); + Type parType = par.getType(); + boolean success = getCandidates( parType, sourceType, methodParCandidates ); + if ( !success ) { + return false; + } + } + if ( !returnType.isVoid() ) { + boolean success = getCandidates( returnType, targetType, methodParCandidates ); + if ( !success ) { return false; } } + return true; } - return true; - } - /** - * @param type the type - * @return {@code true}, if the type represents java.lang.Object - */ - private boolean isJavaLangObject(TypeMirror type) { - return type.getKind() == TypeKind.DECLARED - && ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName().contentEquals( - Object.class.getName() ); - } + /** + * @param aCandidateMethodType parameter type or return type from candidate method + * @param matchingType source type / target type to match + * @param candidates Map, keyed by the method generic parameter, with the candidates + * + * @return false no match or conflict has been found + */ + boolean getCandidates(Type aCandidateMethodType, Type matchingType, Map candidates ) { + + if ( !( aCandidateMethodType.isTypeVar() + || aCandidateMethodType.isArrayTypeVar() + || aCandidateMethodType.isWildCardBoundByTypeVar() + || hasGenericTypeParameters( aCandidateMethodType ) ) ) { + // the typeFromCandidateMethod is not a generic (parameterized) type + return true; + } - private enum Assignability { - VISITED_ASSIGNABLE_FROM, VISITED_ASSIGNABLE_TO - } + boolean foundAMatch = false; - private class TypeMatcher extends SimpleTypeVisitor6 { - private final Assignability assignability; - private final Map genericTypesMap; + for ( Type mthdParType : candidateMethod.getTypeParameters() ) { - public TypeMatcher(Assignability assignability, Map genericTypesMap) { - super( Boolean.FALSE ); // default value - this.assignability = assignability; - this.genericTypesMap = genericTypesMap; - } + // typeFromCandidateMethod itself is a generic type, e.g. String method( T par ); + // typeFromCandidateMethod is a generic arrayType e.g. String method( T[] par ); + // typeFromCandidateMethod is embedded in another type e.g. String method( Callable par ); + // typeFromCandidateMethod is a wildcard, bounded by a typeVar + // e.g. String method( List in ) - @Override - public Boolean visitPrimitive(PrimitiveType t, TypeMirror p) { - return typeUtils.isSameType( t, p ); - } + Type.ResolvedPair resolved = mthdParType.resolveParameterToType( matchingType, aCandidateMethodType ); + if ( resolved.getMatch() == null ) { + // we should be dealing with something containing a type parameter at this point. This is + // covered with the checks above. Therefore resolved itself cannot be null. + // If there is no match here, continue with the next candidate, perhaps there will a match with + // the next method type parameter + continue; + } - @Override - public Boolean visitArray(ArrayType t, TypeMirror p) { + foundAMatch = true; // there is a rare case where we do not arrive here at all. - if ( p.getKind().equals( TypeKind.ARRAY ) ) { - return t.getComponentType().accept( this, ( (ArrayType) p ).getComponentType() ); - } - else { - return Boolean.FALSE; - } - } + // resolved something at this point, a candidate can be fetched or created + TypeVarCandidate typeVarCandidate; + if ( candidates.containsKey( mthdParType ) ) { + typeVarCandidate = candidates.get( mthdParType ); + } + else { + // add a new type + typeVarCandidate = new TypeVarCandidate( ); + candidates.put( mthdParType, typeVarCandidate ); + } - @Override - public Boolean visitDeclared(DeclaredType t, TypeMirror p) { - // its a match when: 1) same kind of type, name is equals, nr of type args are the same - // (type args are checked later). - if ( p.getKind() == TypeKind.DECLARED ) { - DeclaredType t1 = (DeclaredType) p; - if ( assignabilityMatches( t, t1 ) - && t.getTypeArguments().size() == t1.getTypeArguments().size() ) { - for ( int i = 0; i < t.getTypeArguments().size(); i++ ) { - if ( !t.getTypeArguments().get( i ).accept( this, t1.getTypeArguments().get( i ) ) ) { - return Boolean.FALSE; - } + // check what we've resolved + if ( resolved.getParameter().isTypeVar() ) { + // it might be already set, but we just checked if its an equivalent type + if ( typeVarCandidate.match == null ) { + typeVarCandidate.match = resolved.getMatch(); + typeVarCandidate.pairs.add( resolved ); } - return Boolean.TRUE; + else if ( !areEquivalent( resolved.getMatch(), typeVarCandidate.match ) ) { + // type has been resolved twice, but with a different candidate (conflict). Stop + return false; + } + + } + else if ( resolved.getParameter().isArrayTypeVar() + && resolved.getParameter().getComponentType().isAssignableTo( mthdParType ) ) { + // e.g. T map( List in ), the match for T should be assignable + // to the parameter T extends Number + typeVarCandidate.pairs.add( resolved ); + } + else if ( resolved.getParameter().isWildCardBoundByTypeVar() + && resolved.getParameter().getTypeBound().isAssignableTo( mthdParType ) ) { + // e.g. T map( List in ), the match for ? super T should be assignable + // to the parameter T extends Number + typeVarCandidate.pairs.add( resolved ); } else { - return Boolean.FALSE; + // none of the above + return false; } } - else { - return Boolean.FALSE; - } + return foundAMatch; } - private boolean assignabilityMatches(DeclaredType visited, DeclaredType param) { - if ( assignability == Assignability.VISITED_ASSIGNABLE_TO ) { - return typeUtils.isAssignable( toRawType( visited ), toRawType( param ) ); - } - else { - return typeUtils.isAssignable( toRawType( param ), toRawType( visited ) ); + /** + * Checks whether all found candidates are within the bounds of the method type var. For instance + * @ U map( T in ). Note that only the relation between the + * match for U and Callable are checked. Not the correct parameter. + * + * @param methodParCandidates + * + * @return true when all within bounds. + */ + private boolean candidatesWithinBounds(Map methodParCandidates ) { + for ( Map.Entry entry : methodParCandidates.entrySet() ) { + for ( Type bound : entry.getKey().getTypeBounds() ) { + for ( Type.ResolvedPair pair : entry.getValue().pairs ) { + if ( entry.getKey().hasUpperBound() ) { + if ( !pair.getMatch().asRawType().isAssignableTo( bound.asRawType() ) ) { + return false; + } + } + else { + // lower bound + if ( !bound.asRawType().isAssignableTo( pair.getMatch().asRawType() ) ) { + return false; + } + } + } + } } + return true; } - private DeclaredType toRawType(DeclaredType t) { - return typeUtils.getDeclaredType( (TypeElement) t.asElement() ); - } - - @Override - public Boolean visitTypeVariable(TypeVariable t, TypeMirror p) { - if ( genericTypesMap.containsKey( t ) ) { - // when already found, the same mapping should apply - TypeMirror p1 = genericTypesMap.get( t ); - return typeUtils.isSameType( p, p1 ); - } - else { - // check if types are in bound - TypeMirror lowerBound = t.getLowerBound(); - TypeMirror upperBound = t.getUpperBound(); - if ( ( isNullType( lowerBound ) || isSubType( typeUtils, lowerBound, p ) ) - && ( isNullType( upperBound ) || isSubType( typeUtils, p, upperBound ) ) ) { - genericTypesMap.put( t, p ); - return Boolean.TRUE; + private boolean hasGenericTypeParameters(Type typeFromCandidateMethod) { + for ( Type typeParam : typeFromCandidateMethod.getTypeParameters() ) { + if ( typeParam.isTypeVar() || typeParam.isWildCardBoundByTypeVar() || typeParam.isArrayTypeVar() ) { + return true; } else { - return Boolean.FALSE; + if ( hasGenericTypeParameters( typeParam ) ) { + return true; + } } } + return false; } - private boolean isNullType(TypeMirror type) { - return type == null || type.getKind() == TypeKind.NULL; - } - - @Override - public Boolean visitWildcard(WildcardType t, TypeMirror p) { - - // check extends bound - TypeMirror extendsBound = t.getExtendsBound(); - if ( !isNullType( extendsBound ) ) { - switch ( extendsBound.getKind() ) { - case DECLARED: - // for example method: String method(? extends String) - // isSubType checks range [subtype, type], e.g. isSubtype [Object, String]==true - return isSubType( typeUtils, p, extendsBound ); - - case TYPEVAR: - // for example method: T method(? extends T) - // this can be done the directly by checking: ? extends String & Serializable - // this checks the part? - return isWithinBounds( p, getTypeParamFromCandidate( extendsBound ) ); - - default: - // does this situation occur? - return Boolean.FALSE; - } + private Type resolve( Type typeFromCandidateMethod, Map pairs ) { + if ( typeFromCandidateMethod.isTypeVar() || typeFromCandidateMethod.isArrayTypeVar() ) { + return pairs.get( typeFromCandidateMethod ); } - - // check super bound - TypeMirror superBound = t.getSuperBound(); - if ( !isNullType( superBound ) ) { - switch ( superBound.getKind() ) { - case DECLARED: - // for example method: String method(? super String) - // to check super type, we can simply reverse the argument, but that would initially yield - // a result: T method(? super T) - if ( !isWithinBounds( p, typeParameter ) ) { - // this checks the part? - return Boolean.FALSE; + else if ( hasGenericTypeParameters( typeFromCandidateMethod ) ) { + TypeMirror[] typeArgs = new TypeMirror[ typeFromCandidateMethod.getTypeParameters().size() ]; + for ( int i = 0; i < typeFromCandidateMethod.getTypeParameters().size(); i++ ) { + Type typeFromCandidateMethodTypeParameter = typeFromCandidateMethod.getTypeParameters().get( i ); + if ( hasGenericTypeParameters( typeFromCandidateMethodTypeParameter ) ) { + // nested type var, lets resolve some more (recur) + Type matchingType = resolve( typeFromCandidateMethodTypeParameter, pairs ); + if ( matchingType == null ) { + // something went wrong + return null; } - // now, it becomes a bit more hairy. We have the relation (? super T). From T we know that - // it is a subclass of String & Serializable. However, The Java Language Secification, - // Chapter 4.4, states that a bound is either: 'A type variable-', 'A class-' or 'An - // interface-' type followed by further interface types. So we must compare with the first - // argument in the Expression String & Serializable & ..., so, in this case String. - // to check super type, we can simply reverse the argument, but that would initially yield - // a result: ), String is not a type var + typeArgs[i] = typeFromCandidateMethodTypeParameter.getTypeMirror(); + } } + DeclaredType typeArg = typeUtils.getDeclaredType( typeFromCandidateMethod.getTypeElement(), typeArgs ); + return typeFactory.getType( typeArg ); + } + else { + // its not a type var or generic parameterized (e.g. just a plain type) + return typeFromCandidateMethod; } - return Boolean.TRUE; } - } - - /** - * Looks through the list of type parameters of the candidate method for a match - * - * @param t type parameter to match - * - * @return matching type parameter - */ - private TypeParameterElement getTypeParamFromCandidate(TypeMirror t) { - for ( TypeParameterElement candidateTypeParam : candidateMethod.getExecutable().getTypeParameters() ) { - if ( candidateTypeParam.asType().equals( t ) ) { - return candidateTypeParam; + boolean areEquivalent( Type a, Type b ) { + if ( a == null || b == null ) { + return false; } + return a.getBoxedEquivalent().equals( b.getBoxedEquivalent() ); } - return null; } - /** - * checks whether a type t is in bounds of the typeParameter tpe - * - * @return true if within bounds - */ - private boolean isWithinBounds(TypeMirror t, TypeParameterElement tpe) { - List bounds = tpe.getBounds(); - if ( t != null && bounds != null ) { - for ( TypeMirror bound : bounds ) { - if ( !( bound.getKind() == TypeKind.DECLARED && isSubType( typeUtils, t, bound ) ) ) { - return false; - } - } - return true; - } - return false; + private static class TypeVarCandidate { + + private Type match; + private List pairs = new ArrayList<>(); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ParameterProvidedMethods.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ParameterProvidedMethods.java new file mode 100644 index 0000000000..6552e43505 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ParameterProvidedMethods.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.ap.internal.model.source; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.mapstruct.ap.internal.model.common.Parameter; + +/** + * Provides access to the {@link SourceMethod}s that are provided by {@link org.mapstruct.Context} parameters of a + * {@link Method} and maintains the relationship between those methods and their originating parameter. + * + * @author Andreas Gudian + */ +public class ParameterProvidedMethods { + private static final ParameterProvidedMethods EMPTY = + new ParameterProvidedMethods( Collections.emptyMap() ); + + private final Map> parameterToProvidedMethods; + private final Map methodToProvidingParameter; + + private ParameterProvidedMethods(Map> parameterToProvidedMethods) { + this.parameterToProvidedMethods = parameterToProvidedMethods; + this.methodToProvidingParameter = new IdentityHashMap<>(); + for ( Entry> entry : parameterToProvidedMethods.entrySet() ) { + for ( SourceMethod method : entry.getValue() ) { + methodToProvidingParameter.put( method, entry.getKey() ); + } + } + } + + /** + * @param orderedParameters The parameters of which the provided methods are to be returned. + * @return The methods provided by the given parameters in the order as defined by the parameter list, with the + * methods of each parameter ordered based on their definition in that parameter's type. + */ + public List getAllProvidedMethodsInParameterOrder(List orderedParameters) { + List result = new ArrayList<>(); + + for ( Parameter parameter : orderedParameters ) { + List methods = parameterToProvidedMethods.get( parameter ); + if ( methods != null ) { + result.addAll( methods ); + } + } + + return result; + } + + /** + * @param method The method for which the defining parameter is to be returned. + * @return The Parameter on which's type the provided method is defined, or {@code null} if the method was not + * defined on one of the tracked parameters. + */ + public Parameter getParameterForProvidedMethod(Method method) { + return methodToProvidingParameter.get( method ); + } + + /** + * @return {@code true}, if no methods are provided by the tracked parameters or no parameters are tracked at all. + */ + public boolean isEmpty() { + return methodToProvidingParameter.isEmpty(); + } + + public static Builder builder() { + return new Builder(); + } + + public static ParameterProvidedMethods empty() { + return EMPTY; + } + + public static class Builder { + private Map> contextProvidedMethods = + new HashMap<>(); + + private Builder() { + } + + public void addMethodsForParameter(Parameter param, List methods) { + contextProvidedMethods.put( param, methods ); + } + + public ParameterProvidedMethods build() { + return new ParameterProvidedMethods( contextProvidedMethods ); + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java new file mode 100644 index 0000000000..35cdcd9cc0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SelectionParameters.java @@ -0,0 +1,243 @@ +/* + * 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.model.source; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.model.common.SourceRHS; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * Holding parameters common to the selection process, common to IterableMapping, BeanMapping, PropertyMapping and + * MapMapping + * + * @author Sjaak Derksen + */ +public class SelectionParameters { + + private static final SelectionParameters EMPTY = new SelectionParameters( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + null, + null, + null + ); + + private final List qualifiers; + private final List qualifyingNames; + private final List conditionQualifiers; + private final List conditionQualifyingNames; + private final TypeMirror resultType; + private final TypeUtils typeUtils; + private final SourceRHS sourceRHS; + + /** + * Returns new selection parameters + * + * ResultType is not inherited. + * + * @param selectionParameters the selection parameters that need to be copied + * + * @return the selection parameters based on the given ones + */ + public static SelectionParameters forInheritance(SelectionParameters selectionParameters) { + return withoutResultType( selectionParameters ); + } + + public static SelectionParameters withoutResultType(SelectionParameters selectionParameters) { + return new SelectionParameters( + selectionParameters.qualifiers, + selectionParameters.qualifyingNames, + selectionParameters.conditionQualifiers, + selectionParameters.conditionQualifyingNames, + null, + selectionParameters.typeUtils + ); + } + + public SelectionParameters(List qualifiers, List qualifyingNames, TypeMirror resultType, + TypeUtils typeUtils) { + this( + qualifiers, + qualifyingNames, + Collections.emptyList(), + Collections.emptyList(), + resultType, + typeUtils, + null + ); + } + + public SelectionParameters(List qualifiers, List qualifyingNames, + List conditionQualifiers, List conditionQualifyingNames, + TypeMirror resultType, + TypeUtils typeUtils) { + this( qualifiers, qualifyingNames, conditionQualifiers, conditionQualifyingNames, resultType, typeUtils, null ); + } + + private SelectionParameters(List qualifiers, List qualifyingNames, + List conditionQualifiers, List conditionQualifyingNames, + TypeMirror resultType, + TypeUtils typeUtils, SourceRHS sourceRHS) { + this.qualifiers = qualifiers; + this.qualifyingNames = qualifyingNames; + this.conditionQualifiers = conditionQualifiers; + this.conditionQualifyingNames = conditionQualifyingNames; + this.resultType = resultType; + this.typeUtils = typeUtils; + this.sourceRHS = sourceRHS; + } + + /** + * + * @return qualifiers used for further select the appropriate mapping method based on class and name + */ + public List getQualifiers() { + return qualifiers; + } + + /** + * + * @return qualifyingNames see qualifiers, used in combination with with @Named + */ + public List getQualifyingNames() { + return qualifyingNames; + } + + /** + * @return qualifiers used for further select the appropriate presence check method based on class and name + */ + public List getConditionQualifiers() { + return conditionQualifiers; + } + + /** + * @return qualifyingNames, used in combination with with @Named + * @see #getConditionQualifiers() + */ + public List getConditionQualifyingNames() { + return conditionQualifyingNames; + } + + /** + * + * @return resultType used for further select the appropriate mapping method based on resultType (bean mapping) + * targetType (Iterable- and MapMapping) + */ + public TypeMirror getResultType() { + return resultType; + } + + /** + * @return sourceRHS used for further selection of an appropriate factory method + */ + public SourceRHS getSourceRHS() { + return sourceRHS; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + (this.qualifyingNames != null ? this.qualifyingNames.hashCode() : 0); + hash = 97 * hash + (this.resultType != null ? this.resultType.toString().hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + final SelectionParameters other = (SelectionParameters) obj; + + if ( !equals( this.qualifiers, other.qualifiers ) ) { + return false; + } + + if ( !Objects.equals( this.qualifyingNames, other.qualifyingNames ) ) { + return false; + } + + if ( !Objects.equals( this.conditionQualifiers, other.conditionQualifiers ) ) { + return false; + } + + if ( !Objects.equals( this.conditionQualifyingNames, other.conditionQualifyingNames ) ) { + return false; + } + + if ( !Objects.equals( this.sourceRHS, other.sourceRHS ) ) { + return false; + } + + return equals( this.resultType, other.resultType ); + } + + private boolean equals(List mirrors1, List mirrors2) { + if ( mirrors1 == null ) { + return (mirrors2 == null); + } + else if ( mirrors2 == null || mirrors1.size() != mirrors2.size() ) { + return false; + } + + for ( int i = 0; i < mirrors1.size(); i++ ) { + if ( !equals( mirrors1.get( i ), mirrors2.get( i ) ) ) { + return false; + } + } + return true; + } + + private boolean equals(TypeMirror mirror1, TypeMirror mirror2) { + if ( mirror1 == null ) { + return (mirror2 == null); + } + else { + return mirror2 != null && typeUtils.isSameType( mirror1, mirror2 ); + } + } + + public SelectionParameters withSourceRHS(SourceRHS sourceRHS) { + return new SelectionParameters( + this.qualifiers, + this.qualifyingNames, + this.conditionQualifiers, + this.conditionQualifyingNames, + null, + this.typeUtils, + sourceRHS + ); + } + + public static SelectionParameters forSourceRHS(SourceRHS sourceRHS) { + return new SelectionParameters( + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + null, + null, + sourceRHS + ); + } + + public static SelectionParameters empty() { + return EMPTY; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index cad9d9fe0f..8427dd3eca 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -1,58 +1,44 @@ -/** - * Copyright 2012-2015 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.model.source; -import static org.mapstruct.ap.internal.util.Collections.first; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; - +import java.util.stream.Collectors; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.util.Types; +import org.mapstruct.ap.internal.gem.ObjectFactoryGem; import org.mapstruct.ap.internal.model.common.Accessibility; 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.SourceReference.PropertyEntry; import org.mapstruct.ap.internal.util.Executables; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.TypeUtils; + +import static org.mapstruct.ap.internal.model.source.MappingMethodUtils.isEnumMapping; +import static org.mapstruct.ap.internal.util.Collections.first; /** * Represents a mapping method with source and target type and the mappings between the properties of source and target * type. *

    * A method can either be configured by itself or by another method for the inverse mapping direction (the appropriate - * setter on {@link MappingOptions} will be called in this case). + * setter on {@link MappingMethodOptions} will be called in this case). * * @author Gunnar Morling */ public class SourceMethod implements Method { - private final Types typeUtils; + private final TypeUtils typeUtils; private final TypeFactory typeFactory; private final Type declaringMapper; @@ -60,19 +46,34 @@ public class SourceMethod implements Method { private final List parameters; private final Parameter mappingTargetParameter; private final Parameter targetTypeParameter; + private final Parameter sourcePropertyNameParameter; + private final Parameter targetPropertyNameParameter; + private final boolean isObjectFactory; private final Type returnType; private final Accessibility accessibility; private final List exceptionTypes; - private final MapperConfiguration config; - private final MappingOptions mappingOptions; + private final MappingMethodOptions mappingMethodOptions; + private final ConditionMethodOptions conditionMethodOptions; private final List prototypeMethods; private final Type mapperToImplement; - private List sourceParameters; + private final List sourceParameters; + private final List contextParameters; + private final ParameterProvidedMethods contextProvidedMethods; + private final List typeParameters; private List parameterNames; private List applicablePrototypeMethods; + private List applicableReversePrototypeMethods; + + private Boolean isValueMapping; + private Boolean isIterableMapping; + private Boolean isMapMapping; + private Boolean isStreamMapping; + private final boolean hasObjectFactoryAnnotation; + + private final boolean verboseLogging; public static class Builder { @@ -82,18 +83,23 @@ public static class Builder { private List parameters; private Type returnType = null; private List exceptionTypes; - private Map> mappings; - private IterableMapping iterableMapping = null; - private MapMapping mapMapping = null; - private BeanMapping beanMapping = null; - private Types typeUtils; + private Set mappings; + private IterableMappingOptions iterableMapping = null; + private MapMappingOptions mapMapping = null; + private BeanMappingOptions beanMapping = null; + private TypeUtils typeUtils; private TypeFactory typeFactory = null; - private FormattingMessager messager = null; - private MapperConfiguration mapperConfig = null; + private MapperOptions mapper = null; private List prototypeMethods = Collections.emptyList(); + private List valueMappings; + private EnumMappingOptions enumMappingOptions; + private ParameterProvidedMethods contextProvidedMethods; + private Set conditionOptions; + private List typeParameters; + private Set subclassMappings; - public Builder() { - } + private boolean verboseLogging; + private SubclassValidator subclassValidator; public Builder setDeclaringMapper(Type declaringMapper) { this.declaringMapper = declaringMapper; @@ -120,43 +126,58 @@ public Builder setExceptionTypes(List exceptionTypes) { return this; } - public Builder setMappings(Map> mappings) { + public Builder setMappingOptions(Set mappings) { this.mappings = mappings; return this; } - public Builder setIterableMapping(IterableMapping iterableMapping) { + public Builder setIterableMappingOptions(IterableMappingOptions iterableMapping) { this.iterableMapping = iterableMapping; return this; } - public Builder setMapMapping(MapMapping mapMapping) { + public Builder setMapMappingOptions(MapMappingOptions mapMapping) { this.mapMapping = mapMapping; return this; } - public Builder setBeanMapping(BeanMapping beanMapping) { + public Builder setBeanMappingOptions(BeanMappingOptions beanMapping) { this.beanMapping = beanMapping; return this; } - public Builder setTypeUtils(Types typeUtils) { - this.typeUtils = typeUtils; + public Builder setValueMappingOptionss(List valueMappings) { + this.valueMappings = valueMappings; return this; } - public Builder setTypeFactory(TypeFactory typeFactory) { - this.typeFactory = typeFactory; + public Builder setEnumMappingOptions(EnumMappingOptions enumMappingOptions) { + this.enumMappingOptions = enumMappingOptions; return this; } - public Builder setMessager(FormattingMessager messager) { - this.messager = messager; + public Builder setSubclassMappings(Set subclassMappings) { + this.subclassMappings = subclassMappings; return this; } - public Builder setMapperConfiguration(MapperConfiguration mapperConfig) { - this.mapperConfig = mapperConfig; + public Builder setSubclassValidator(SubclassValidator subclassValidator) { + this.subclassValidator = subclassValidator; + return this; + } + + public Builder setTypeUtils(TypeUtils typeUtils) { + this.typeUtils = typeUtils; + return this; + } + + public Builder setTypeFactory(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + return this; + } + + public Builder setMapper(MapperOptions mapper) { + this.mapper = mapper; return this; } @@ -165,89 +186,104 @@ public Builder setPrototypeMethods(List prototypeMethods) { return this; } - public Builder setDefininingType(Type definingType) { + public Builder setDefiningType(Type definingType) { this.definingType = definingType; return this; } + public Builder setContextProvidedMethods(ParameterProvidedMethods contextProvidedMethods) { + this.contextProvidedMethods = contextProvidedMethods; + return this; + } + + public Builder setConditionOptions(Set conditionOptions) { + this.conditionOptions = conditionOptions; + return this; + } + + public Builder setVerboseLogging(boolean verboseLogging) { + this.verboseLogging = verboseLogging; + return this; + } + public SourceMethod build() { - MappingOptions mappingOptions - = new MappingOptions( mappings, iterableMapping, mapMapping, beanMapping ); - - - SourceMethod sourceMethod = new SourceMethod( - declaringMapper, - executable, - parameters, - returnType, - exceptionTypes, - mappingOptions, - typeUtils, - typeFactory, - mapperConfig, - prototypeMethods, - definingType - ); + if ( mappings == null ) { + mappings = Collections.emptySet(); + } - if ( mappings != null ) { - for ( Map.Entry> entry : mappings.entrySet() ) { - for ( Mapping mapping : entry.getValue() ) { - mapping.init( sourceMethod, messager, typeFactory ); - } - } + if ( subclassMappings == null ) { + subclassMappings = Collections.emptySet(); } - return sourceMethod; + + MappingMethodOptions mappingMethodOptions = new MappingMethodOptions( + mapper, + mappings, + iterableMapping, + mapMapping, + beanMapping, + enumMappingOptions, + valueMappings, + subclassMappings, + subclassValidator + ); + + ConditionMethodOptions conditionMethodOptions = + conditionOptions != null ? new ConditionMethodOptions( conditionOptions ) : + ConditionMethodOptions.empty(); + + this.typeParameters = this.executable.getTypeParameters() + .stream() + .map( Element::asType ) + .map( typeFactory::getType ) + .collect( Collectors.toList() ); + + return new SourceMethod( this, mappingMethodOptions, conditionMethodOptions ); } } - @SuppressWarnings("checkstyle:parameternumber") - private SourceMethod(Type declaringMapper, ExecutableElement executable, List parameters, - Type returnType, List exceptionTypes, MappingOptions mappingOptions, Types typeUtils, - TypeFactory typeFactory, MapperConfiguration config, List prototypeMethods, - Type mapperToImplement) { - this.declaringMapper = declaringMapper; - this.executable = executable; - this.parameters = parameters; - this.returnType = returnType; - this.exceptionTypes = exceptionTypes; - this.accessibility = Accessibility.fromModifiers( executable.getModifiers() ); + private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions, + ConditionMethodOptions conditionMethodOptions) { + this.declaringMapper = builder.declaringMapper; + this.executable = builder.executable; + this.parameters = builder.parameters; + this.returnType = builder.returnType; + this.exceptionTypes = builder.exceptionTypes; + this.accessibility = Accessibility.fromModifiers( builder.executable.getModifiers() ); - this.mappingOptions = mappingOptions; + this.mappingMethodOptions = mappingMethodOptions; + this.conditionMethodOptions = conditionMethodOptions; - this.mappingTargetParameter = determineMappingTargetParameter( parameters ); - this.targetTypeParameter = determineTargetTypeParameter( parameters ); + this.sourceParameters = Parameter.getSourceParameters( parameters ); + this.contextParameters = Parameter.getContextParameters( parameters ); + this.contextProvidedMethods = builder.contextProvidedMethods; + this.typeParameters = builder.typeParameters; - this.typeUtils = typeUtils; - this.typeFactory = typeFactory; - this.config = config; - this.prototypeMethods = prototypeMethods; - this.mapperToImplement = mapperToImplement; - } + this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); + this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); + this.sourcePropertyNameParameter = Parameter.getSourcePropertyNameParameter( parameters ); + this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters ); + this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null; + this.isObjectFactory = determineIfIsObjectFactory(); - private Parameter determineMappingTargetParameter(Iterable parameters) { - for ( Parameter parameter : parameters ) { - if ( parameter.isMappingTarget() ) { - return parameter; - } - } + this.typeUtils = builder.typeUtils; + this.typeFactory = builder.typeFactory; + this.prototypeMethods = builder.prototypeMethods; + this.mapperToImplement = builder.definingType; - return null; + this.verboseLogging = builder.verboseLogging; } - private Parameter determineTargetTypeParameter(Iterable parameters) { - for ( Parameter parameter : parameters ) { - if ( parameter.isTargetType() ) { - return parameter; - } - } - - return null; + private boolean determineIfIsObjectFactory() { + boolean hasNoSourceParameters = getSourceParameters().isEmpty(); + boolean hasNoMappingTargetParam = getMappingTargetParameter() == null; + boolean hasNoSourcePropertyNameParam = getSourcePropertyNameParameter() == null; + boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null; + return !isLifecycleCallbackMethod() && !returnType.isVoid() + && hasNoMappingTargetParam && hasNoSourcePropertyNameParam && hasNoTargetPropertyNameParam + && ( hasObjectFactoryAnnotation || hasNoSourceParameters ); } - /** - * {@inheritDoc} {@link Method} - */ @Override public Type getDeclaringMapper() { return declaringMapper; @@ -258,48 +294,41 @@ public ExecutableElement getExecutable() { return executable; } - /** - * {@inheritDoc} {@link Method} - */ @Override public String getName() { return executable.getSimpleName().toString(); } - /** - * {@inheritDoc} {@link Method} - */ @Override public List getParameters() { return parameters; } - /** - * {@inheritDoc} {@link Method} - */ @Override public List getSourceParameters() { - if ( sourceParameters == null ) { - sourceParameters = new ArrayList(); + return sourceParameters; + } - for ( Parameter parameter : parameters ) { - if ( !parameter.isMappingTarget() && !parameter.isTargetType() ) { - sourceParameters.add( parameter ); - } - } - } + @Override + public List getContextParameters() { + return contextParameters; + } - return sourceParameters; + @Override + public ParameterProvidedMethods getContextProvidedMethods() { + return contextProvidedMethods; } @Override public List getParameterNames() { if ( parameterNames == null ) { - parameterNames = new ArrayList( parameters.size() ); + List names = new ArrayList<>( parameters.size() ); for ( Parameter parameter : parameters ) { - parameterNames.add( parameter.getName() ); + names.add( parameter.getName() ); } + + parameterNames = Collections.unmodifiableList( names ); } return parameterNames; @@ -310,9 +339,6 @@ public Type getResultType() { return mappingTargetParameter != null ? mappingTargetParameter.getType() : returnType; } - /** - * {@inheritDoc} {@link Method} - */ @Override public Type getReturnType() { return returnType; @@ -323,28 +349,20 @@ public Accessibility getAccessibility() { return accessibility; } - public Mapping getSingleMappingByTargetPropertyName(String targetPropertyName) { - List all = mappingOptions.getMappings().get( targetPropertyName ); - return all != null ? first( all ) : null; - } - - public boolean reverses(SourceMethod method) { - return getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1 - && equals( first( getSourceParameters() ).getType(), method.getResultType() ) - && equals( getResultType(), first( method.getSourceParameters() ).getType() ); - } - - public boolean isSame(SourceMethod method) { - return getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1 - && equals( first( getSourceParameters() ).getType(), - first( method.getSourceParameters() ).getType() ) - && equals( getResultType(), method.getResultType() ); + public boolean inverses(SourceMethod method) { + return method.getDeclaringMapper() == null + && method.isAbstract() + && getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1 + && getMappingSourceType().isAssignableTo( method.getResultType() ) + && getResultType().isAssignableTo( first( method.getSourceParameters() ).getType() ); } public boolean canInheritFrom(SourceMethod method) { - return isMapMapping() == method.isMapMapping() + return method.getDeclaringMapper() == null + && method.isAbstract() + && isMapMapping() == method.isMapMapping() && isIterableMapping() == method.isIterableMapping() - && isEnumMapping() == method.isEnumMapping() + && isEnumMapping( this ) == isEnumMapping( method ) && getResultType().isAssignableTo( method.getResultType() ) && allParametersAreAssignable( getSourceParameters(), method.getSourceParameters() ); } @@ -354,28 +372,74 @@ public Parameter getMappingTargetParameter() { return mappingTargetParameter; } + @Override + public boolean isObjectFactory() { + return isObjectFactory; + } + @Override public Parameter getTargetTypeParameter() { return targetTypeParameter; } + public Parameter getSourcePropertyNameParameter() { + return sourcePropertyNameParameter; + } + + public Parameter getTargetPropertyNameParameter() { + return targetPropertyNameParameter; + } + public boolean isIterableMapping() { - return getSourceParameters().size() == 1 && first( getSourceParameters() ).getType().isIterableType() - && getResultType().isIterableType(); + if ( isIterableMapping == null ) { + isIterableMapping = getSourceParameters().size() == 1 + && getMappingSourceType().isIterableType() + && getResultType().isIterableType(); + } + return isIterableMapping; + } + + public boolean isStreamMapping() { + if ( isStreamMapping == null ) { + isStreamMapping = getSourceParameters().size() == 1 + && ( getMappingSourceType().isIterableType() && getResultType().isStreamType() + || getMappingSourceType().isStreamType() && getResultType().isIterableType() + || getMappingSourceType().isStreamType() && getResultType().isStreamType() ); + } + return isStreamMapping; } public boolean isMapMapping() { - return getSourceParameters().size() == 1 && first( getSourceParameters() ).getType().isMapType() - && getResultType().isMapType(); + if ( isMapMapping == null ) { + isMapMapping = getSourceParameters().size() == 1 + && getMappingSourceType().isMapType() + && getResultType().isMapType(); + } + return isMapMapping; } - public boolean isEnumMapping() { - return getSourceParameters().size() == 1 && first( getSourceParameters() ).getType().isEnumType() - && getResultType().isEnumType(); + /** + * Enum Mapping was realized with @Mapping in stead of @ValueMapping. @Mapping is no longer + * supported. + * + * @return true when @Mapping is used in stead of @ValueMapping + */ + public boolean isRemovedEnumMapping() { + return MappingMethodUtils.isEnumMapping( this ); } - private boolean equals(Object o1, Object o2) { - return (o1 == null && o2 == null) || (o1 != null) && o1.equals( o2 ); + /** + * The default enum mapping (no mappings specified) will from now on be handled as a value mapping. If there + * are any @Mapping / @Mappings defined on the method, then the deprecated enum behavior should be executed. + * + * @return whether (true) or not (false) to execute value mappings + */ + public boolean isValueMapping() { + + if ( isValueMapping == null ) { + isValueMapping = isEnumMapping( this ) && mappingMethodOptions.getMappings().isEmpty(); + } + return isValueMapping; } @Override @@ -392,66 +456,37 @@ public String toString() { return sb.toString(); } - /** - * Returns the {@link Mapping}s for the given source property. - * - * @param sourcePropertyName the source property name - * - * @return list of mappings - */ - public List getMappingBySourcePropertyName(String sourcePropertyName) { - List mappingsOfSourceProperty = new ArrayList(); - - for ( List mappingOfProperty : mappingOptions.getMappings().values() ) { - for ( Mapping mapping : mappingOfProperty ) { - - if ( isEnumMapping() ) { - if ( mapping.getSourceName().equals( sourcePropertyName ) ) { - mappingsOfSourceProperty.add( mapping ); - } - } - else { - List sourceEntries = mapping.getSourceReference().getPropertyEntries(); + public List getApplicablePrototypeMethods() { + if ( applicablePrototypeMethods == null ) { + applicablePrototypeMethods = new ArrayList<>(); - // there can only be a mapping if there's only one entry for a source property, so: param.property. - // There can be no mapping if there are more entries. So: param.property.property2 - if ( sourceEntries.size() == 1 && sourcePropertyName.equals( first( sourceEntries ).getName() ) ) { - mappingsOfSourceProperty.add( mapping ); - } + for ( SourceMethod prototype : prototypeMethods ) { + if ( canInheritFrom( prototype ) ) { + applicablePrototypeMethods.add( prototype ); } } } - return mappingsOfSourceProperty; - } - - public Parameter getSourceParameter(String sourceParameterName) { - for ( Parameter parameter : getSourceParameters() ) { - if ( parameter.getName().equals( sourceParameterName ) ) { - return parameter; - } - } - - return null; + return applicablePrototypeMethods; } - public List getApplicablePrototypeMethods() { - if ( applicablePrototypeMethods == null ) { - applicablePrototypeMethods = new ArrayList(); + public List getApplicableReversePrototypeMethods() { + if ( applicableReversePrototypeMethods == null ) { + applicableReversePrototypeMethods = new ArrayList<>(); for ( SourceMethod prototype : prototypeMethods ) { - if ( canInheritFrom( prototype ) ) { - applicablePrototypeMethods.add( prototype ); + if ( inverses( prototype ) ) { + applicableReversePrototypeMethods.add( prototype ); } } } - return applicablePrototypeMethods; + return applicableReversePrototypeMethods; } private static boolean allParametersAreAssignable(List fromParams, List toParams) { if ( fromParams.size() == toParams.size() ) { - Set unaccountedToParams = new HashSet( toParams ); + Set unaccountedToParams = new HashSet<>( toParams ); for ( Parameter fromParam : fromParams ) { // each fromParam needs at least one match, and all toParam need to be accounted for at the end @@ -484,9 +519,6 @@ public boolean overridesMethod() { return declaringMapper == null && executable.getModifiers().contains( Modifier.ABSTRACT ); } - /** - * {@inheritDoc} {@link Method} - */ @Override public boolean matches(List sourceTypes, Type targetType) { MethodMatcher matcher = new MethodMatcher( typeUtils, typeFactory, this ); @@ -499,13 +531,7 @@ public boolean matches(List sourceTypes, Type targetType) { * @return {@code true} if the parameter list contains a parameter annotated with {@code @TargetType} */ public static boolean containsTargetTypeParameter(List parameters) { - for ( Parameter param : parameters ) { - if ( param.isTargetType() ) { - return true; - } - } - - return false; + return parameters.stream().anyMatch( Parameter::isTargetType ); } @Override @@ -513,8 +539,14 @@ public List getThrownTypes() { return exceptionTypes; } - public MappingOptions getMappingOptions() { - return mappingOptions; + @Override + public MappingMethodOptions getOptions() { + return mappingMethodOptions; + } + + @Override + public ConditionMethodOptions getConditionOptions() { + return conditionMethodOptions; } @Override @@ -532,11 +564,6 @@ public Type getDefiningType() { return mapperToImplement; } - @Override - public MapperConfiguration getMapperConfiguration() { - return config; - } - @Override public boolean isLifecycleCallbackMethod() { return Executables.isLifecycleCallbackMethod( getExecutable() ); @@ -550,8 +577,39 @@ public boolean isBeforeMappingMethod() { return Executables.isBeforeMappingMethod( getExecutable() ); } + /** + * @return returns true for interface methods (see jls 9.4) lacking a default or static modifier and for abstract + * methods + */ + public boolean isAbstract() { + return executable.getModifiers().contains( Modifier.ABSTRACT ); + } + @Override public boolean isUpdateMethod() { return getMappingTargetParameter() != null; } + + public boolean hasObjectFactoryAnnotation() { + return hasObjectFactoryAnnotation; + } + + @Override + public List getTypeParameters() { + return this.typeParameters; + } + + @Override + public String describe() { + if ( verboseLogging ) { + return toString(); + } + else { + String mapper = declaringMapper != null ? declaringMapper.getName() + "." : ""; + String sourceTypes = getParameters().stream() + .map( Parameter::describe ) + .collect( Collectors.joining( ", " ) ); + return getResultType().describe() + " " + mapper + getName() + "(" + sourceTypes + ")"; + } + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceReference.java deleted file mode 100644 index 088123d5d1..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceReference.java +++ /dev/null @@ -1,314 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.source; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import javax.lang.model.element.ExecutableElement; - -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.util.FormattingMessager; -import org.mapstruct.ap.internal.util.Message; -import org.mapstruct.ap.internal.util.Strings; - -import static org.mapstruct.ap.internal.util.Collections.first; - -/** - * This class describes the source side of a property mapping. - *

    - * It contains the source parameter, and all individual (nested) property entries. So consider the following - * mapping method: - * - *

    - * @Mapping(source = "in.propA.propB" target = "propC")
    - * TypeB mappingMethod(TypeA in);
    - * 
    - * - * Then: - *
      - *
    • {@code parameter} will describe {@code in}
    • - *
    • {@code propertyEntries[0]} will describe {@code propA}
    • - *
    • {@code propertyEntries[1]} will describe {@code propB}
    • - *
    - * - * After building, {@link #isValid()} will return true when when no problems are detected during building. - * - * @author Sjaak Derksen - */ -public class SourceReference { - - private final Parameter parameter; - private final List propertyEntries; - private final boolean isValid; - - /** - * Builds a {@link SourceReference} from an {@code @Mappping}. - */ - public static class BuilderFromMapping { - - private Mapping mapping; - private SourceMethod method; - private FormattingMessager messager; - private TypeFactory typeFactory; - - public BuilderFromMapping messager(FormattingMessager messager) { - this.messager = messager; - return this; - } - - public BuilderFromMapping mapping(Mapping mapping) { - this.mapping = mapping; - return this; - } - - public BuilderFromMapping method(SourceMethod method) { - this.method = method; - return this; - } - - public BuilderFromMapping typeFactory(TypeFactory typeFactory) { - this.typeFactory = typeFactory; - return this; - } - - public SourceReference build() { - - String sourceName = mapping.getSourceName(); - - if ( sourceName == null ) { - return null; - } - - boolean isValid = true; - boolean foundEntryMatch; - - String[] sourcePropertyNames = new String[0]; - String[] segments = sourceName.split( "\\." ); - Parameter parameter = null; - - List entries = new ArrayList(); - - if ( method.getSourceParameters().size() > 1 ) { - - // parameterName is mandatory for multiple source parameters - if ( segments.length > 0 ) { - String sourceParameterName = segments[0]; - parameter = method.getSourceParameter( sourceParameterName ); - if ( parameter == null ) { - reportMappingError( Message.PROPERTYMAPPING_INVALID_PARAMETER_NAME, sourceParameterName ); - isValid = false; - } - } - if ( segments.length > 1 && parameter != null ) { - sourcePropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = getSourceEntries( parameter.getType(), sourcePropertyNames ); - foundEntryMatch = ( entries.size() == sourcePropertyNames.length ); - } - else { - // its only a parameter, no property - foundEntryMatch = true; - } - - } - else { - - // parameter name is not mandatory for single source parameter - sourcePropertyNames = segments; - parameter = method.getSourceParameters().get( 0 ); - entries = getSourceEntries( parameter.getType(), sourcePropertyNames ); - foundEntryMatch = ( entries.size() == sourcePropertyNames.length ); - - if ( !foundEntryMatch ) { - //Lets see if the expression contains the parameterName, so parameterName.propName1.propName2 - if ( parameter.getName().equals( segments[0] ) ) { - sourcePropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = getSourceEntries( parameter.getType(), sourcePropertyNames ); - foundEntryMatch = ( entries.size() == sourcePropertyNames.length ); - } - else { - // segment[0] cannot be attributed to the parameter name. - parameter = null; - } - } - } - - if ( !foundEntryMatch ) { - - if ( parameter != null ) { - reportMappingError( Message.PROPERTYMAPPING_NO_PROPERTY_IN_PARAMETER, parameter.getName(), - Strings.join( Arrays.asList( sourcePropertyNames ), "." ) ); - } - else { - reportMappingError( Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME, mapping.getSourceName() ); - } - isValid = false; - } - - return new SourceReference( parameter, entries, isValid ); - } - - private List getSourceEntries(Type type, String[] entryNames) { - List sourceEntries = new ArrayList(); - Type newType = type; - for ( String entryName : entryNames ) { - boolean matchFound = false; - Map sourceReadAccessors = newType.getPropertyReadAccessors(); - for ( Map.Entry getter : sourceReadAccessors.entrySet() ) { - if ( getter.getKey().equals( entryName ) ) { - newType = typeFactory.getType( getter.getValue().getReturnType() ); - sourceEntries.add( new PropertyEntry( entryName, getter.getValue(), newType ) ); - matchFound = true; - break; - } - } - if ( !matchFound ) { - break; - } - } - return sourceEntries; - } - - private void reportMappingError(Message msg, Object... objects) { - messager.printMessage( method.getExecutable(), mapping.getMirror(), mapping.getSourceAnnotationValue(), - msg, objects ); - } - } - - /** - * Builds a {@link SourceReference} from a property. - */ - public static class BuilderFromProperty { - - private String name; - private ExecutableElement accessor; - private Type type; - private Parameter sourceParameter; - - public BuilderFromProperty name(String name) { - this.name = name; - return this; - } - - public BuilderFromProperty accessor(ExecutableElement accessor) { - this.accessor = accessor; - return this; - } - - public BuilderFromProperty type(Type type) { - this.type = type; - return this; - } - - public BuilderFromProperty sourceParameter(Parameter sourceParameter) { - this.sourceParameter = sourceParameter; - return this; - } - - public SourceReference build() { - List sourcePropertyEntries = new ArrayList(); - if ( accessor != null ) { - sourcePropertyEntries.add( new PropertyEntry( name, accessor, type ) ); - } - return new SourceReference( sourceParameter, sourcePropertyEntries, true ); - } - } - - private SourceReference(Parameter sourceParameter, List sourcePropertyEntries, boolean isValid) { - this.parameter = sourceParameter; - this.propertyEntries = sourcePropertyEntries; - this.isValid = isValid; - } - - public Parameter getParameter() { - return parameter; - } - - public List getPropertyEntries() { - return propertyEntries; - } - - public boolean isValid() { - return isValid; - } - - public List getElementNames() { - List sourceName = new ArrayList(); - sourceName.add( parameter.getName() ); - for ( PropertyEntry propertyEntry : propertyEntries ) { - sourceName.add( propertyEntry.getName() ); - } - return sourceName; - } - - /** - * A PropertyEntry contains information on the name, accessor and return type of a property. - */ - public static class PropertyEntry { - - private final String name; - private final ExecutableElement accessor; - private final Type type; - - public PropertyEntry(String name, ExecutableElement accessor, Type type) { - this.name = name; - this.accessor = accessor; - this.type = type; - } - - public String getName() { - return name; - } - - public ExecutableElement getAccessor() { - return accessor; - } - - public Type getType() { - return type; - } - - } - - /** - * Creates a copy of this reference, which is adapted to the given method - * - * @param method the method to create the copy for - * @return the copy - */ - public SourceReference copyForInheritanceTo(SourceMethod method) { - List replacementParamCandidates = new ArrayList(); - for ( Parameter sourceParam : method.getSourceParameters() ) { - if ( sourceParam.getType().isAssignableTo( parameter.getType() ) ) { - replacementParamCandidates.add( sourceParam ); - } - } - - Parameter replacement = parameter; - if ( replacementParamCandidates.size() == 1 ) { - replacement = first( replacementParamCandidates ); - } - - return new SourceReference( replacement, propertyEntries, isValid ); - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java new file mode 100644 index 0000000000..2b0e6dd13e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassMappingOptions.java @@ -0,0 +1,235 @@ +/* + * 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.model.source; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.gem.SubclassMappingGem; +import org.mapstruct.ap.internal.gem.SubclassMappingsGem; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; + +import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_ILLEGAL_SUBCLASS; +import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_NO_VALID_SUPERCLASS; +import static org.mapstruct.ap.internal.util.Message.SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED; + +/** + * Represents a subclass mapping as configured via {@code @SubclassMapping}. + * + * @author Ben Zegveld + */ +public class SubclassMappingOptions extends DelegatingOptions { + + private final TypeMirror source; + private final TypeMirror target; + private final TypeUtils typeUtils; + private final SelectionParameters selectionParameters; + private final SubclassMappingGem subclassMapping; + + public SubclassMappingOptions(TypeMirror source, TypeMirror target, TypeUtils typeUtils, DelegatingOptions next, + SelectionParameters selectionParameters, SubclassMappingGem subclassMapping) { + super( next ); + this.source = source; + this.target = target; + this.typeUtils = typeUtils; + this.selectionParameters = selectionParameters; + this.subclassMapping = subclassMapping; + } + + @Override + public boolean hasAnnotation() { + return source != null && target != null; + } + + private static boolean isConsistent(SubclassMappingGem gem, ExecutableElement method, FormattingMessager messager, + TypeUtils typeUtils, List sourceParameters, Type resultType, + SubclassValidator subclassValidator) { + + if ( resultType == null ) { + messager.printMessage( method, gem.mirror(), SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED ); + return false; + } + + TypeMirror sourceSubclass = gem.source().getValue(); + TypeMirror targetSubclass = gem.target().getValue(); + TypeMirror targetParentType = resultType.getTypeMirror(); + validateTypeMirrors( sourceSubclass, targetSubclass, targetParentType ); + + boolean isConsistent = true; + + boolean isChildOfAParameter = false; + for ( Parameter sourceParameter : sourceParameters ) { + TypeMirror sourceParentType = sourceParameter.getType().getTypeMirror(); + validateTypeMirrors( sourceParentType ); + isChildOfAParameter = isChildOfAParameter || isChildOfParent( typeUtils, sourceSubclass, sourceParentType ); + } + if ( !isChildOfAParameter ) { + messager + .printMessage( + method, + gem.mirror(), + SUBCLASSMAPPING_NO_VALID_SUPERCLASS, + sourceSubclass.toString() ); + isConsistent = false; + } + if ( !isChildOfParent( typeUtils, targetSubclass, targetParentType ) ) { + messager + .printMessage( + method, + gem.mirror(), + SUBCLASSMAPPING_ILLEGAL_SUBCLASS, + targetParentType.toString(), + targetSubclass.toString() ); + isConsistent = false; + } + if ( !subclassValidator.isValidUsage( method, gem.mirror(), sourceSubclass ) ) { + isConsistent = false; + } + return isConsistent; + } + + private static void validateTypeMirrors(TypeMirror... typeMirrors) { + for ( TypeMirror typeMirror : typeMirrors ) { + if ( typeMirror == null ) { + // When a class used in uses or imports is created by another annotation processor + // then javac will not return correct TypeMirror with TypeKind#ERROR, but rather a string "" + // the gem tools would return a null TypeMirror in that case. + // Therefore throw TypeHierarchyErroneousException so we can postpone the generation of the mapper + throw new TypeHierarchyErroneousException( typeMirror ); + } + } + } + + private static boolean isChildOfParent(TypeUtils typeUtils, TypeMirror childType, TypeMirror parentType) { + return typeUtils.isSubtype( childType, parentType ); + } + + public TypeMirror getSource() { + return source; + } + + public TypeMirror getTarget() { + return target; + } + + public SelectionParameters getSelectionParameters() { + return selectionParameters; + } + + public AnnotationMirror getMirror() { + return Optional.ofNullable( subclassMapping ).map( SubclassMappingGem::mirror ).orElse( null ); + } + + public static void addInstances(SubclassMappingsGem gem, ExecutableElement method, + BeanMappingOptions beanMappingOptions, FormattingMessager messager, + TypeUtils typeUtils, Set mappings, + List sourceParameters, Type resultType, + SubclassValidator subclassValidator) { + for ( SubclassMappingGem subclassMapping : gem.value().get() ) { + addInstance( + subclassMapping, + method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType, + subclassValidator ); + } + } + + public static void addInstance(SubclassMappingGem subclassMapping, ExecutableElement method, + BeanMappingOptions beanMappingOptions, FormattingMessager messager, + TypeUtils typeUtils, Set mappings, + List sourceParameters, Type resultType, + SubclassValidator subclassValidator) { + if ( !isConsistent( + subclassMapping, + method, + messager, + typeUtils, + sourceParameters, + resultType, + subclassValidator ) ) { + return; + } + + TypeMirror sourceSubclass = subclassMapping.source().getValue(); + TypeMirror targetSubclass = subclassMapping.target().getValue(); + SelectionParameters selectionParameters = new SelectionParameters( + subclassMapping.qualifiedBy().get(), + subclassMapping.qualifiedByName().get(), + targetSubclass, + typeUtils + ); + + mappings + .add( + new SubclassMappingOptions( + sourceSubclass, + targetSubclass, + typeUtils, + beanMappingOptions, + selectionParameters, + subclassMapping + ) ); + } + + public static List copyForInverseInheritance(Set mappings, + BeanMappingOptions beanMappingOptions) { + // we are not interested in keeping it unique at this point. + return mappings.stream().map( mapping -> new SubclassMappingOptions( + mapping.target, + mapping.source, + mapping.typeUtils, + beanMappingOptions, + mapping.selectionParameters, + mapping.subclassMapping + ) ).collect( Collectors.toCollection( ArrayList::new ) ); + } + + public static List copyForInheritance(Set subclassMappings, + BeanMappingOptions beanMappingOptions) { + // we are not interested in keeping it unique at this point. + List mappings = new ArrayList<>(); + for ( SubclassMappingOptions subclassMapping : subclassMappings ) { + mappings.add( + new SubclassMappingOptions( + subclassMapping.source, + subclassMapping.target, + subclassMapping.typeUtils, + beanMappingOptions, + subclassMapping.selectionParameters, + subclassMapping.subclassMapping ) ); + } + return mappings; + } + + @Override + public boolean equals(Object obj) { + if ( obj == null || !( obj instanceof SubclassMappingOptions ) ) { + return false; + } + SubclassMappingOptions other = (SubclassMappingOptions) obj; + return typeUtils.isSameType( source, other.source ); + } + + @Override + public int hashCode() { + return 1; // use a stable value because TypeMirror is not safe to use for hashCode. + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.java new file mode 100644 index 0000000000..601b588e42 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SubclassValidator.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.ap.internal.model.source; + +import java.util.ArrayList; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * Handles the validation of multiple @SubclassMapping annotations on the same method. + * + * @author Ben Zegveld + */ +public class SubclassValidator { + + private final FormattingMessager messager; + private final List handledSubclasses = new ArrayList<>(); + private final TypeUtils typeUtils; + + public SubclassValidator(FormattingMessager messager, TypeUtils typeUtils) { + this.messager = messager; + this.typeUtils = typeUtils; + } + + public boolean isValidUsage(Element e, AnnotationMirror annotation, TypeMirror sourceType) { + for ( TypeMirror typeMirror : handledSubclasses ) { + if ( typeUtils.isSameType( sourceType, typeMirror ) ) { + messager + .printMessage( + e, + annotation, + Message.SUBCLASSMAPPING_DOUBLE_SOURCE_SUBCLASS, + sourceType ); + return false; + } + if ( typeUtils.isAssignable( sourceType, typeMirror ) ) { + messager + .printMessage( + e, + annotation, + Message.SUBCLASSMAPPING_ILLOGICAL_ORDER, + sourceType, + typeMirror, + sourceType, + typeMirror ); + return false; + } + } + handledSubclasses.add( sourceType ); + return true; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java new file mode 100644 index 0000000000..9c412733c4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMappingOptions.java @@ -0,0 +1,148 @@ +/* + * 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.model.source; + +import java.util.Objects; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.ExecutableElement; + +import org.mapstruct.ap.internal.gem.ValueMappingGem; +import org.mapstruct.ap.internal.gem.ValueMappingsGem; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; + +import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_REMAINING; +import static org.mapstruct.ap.internal.gem.MappingConstantsGem.ANY_UNMAPPED; +import static org.mapstruct.ap.internal.gem.MappingConstantsGem.THROW_EXCEPTION; + +/** + * Represents the mapping between one value constant and another. + * + * @author Sjaak Derksen + */ +public class ValueMappingOptions { + + private final String source; + private final String target; + private final AnnotationMirror mirror; + private final AnnotationValue sourceAnnotationValue; + private final AnnotationValue targetAnnotationValue; + + public static void fromMappingsGem(ValueMappingsGem mappingsGem, ExecutableElement method, + FormattingMessager messager, Set mappings) { + + boolean anyFound = false; + for ( ValueMappingGem mappingGem : mappingsGem.value().get() ) { + ValueMappingOptions mapping = fromMappingGem( mappingGem ); + if ( mapping != null ) { + + if ( !mappings.contains( mapping ) ) { + mappings.add( mapping ); + } + else { + messager.printMessage( + method, + mappingGem.mirror(), + mappingGem.target().getAnnotationValue(), + Message.VALUEMAPPING_DUPLICATE_SOURCE, + mappingGem.source().get() + ); + } + if ( ANY_REMAINING.equals( mapping.source ) + || ANY_UNMAPPED.equals( mapping.source ) ) { + if ( anyFound ) { + messager.printMessage( + method, + mappingGem.mirror(), + mappingGem.target().getAnnotationValue(), + Message.VALUEMAPPING_ANY_AREADY_DEFINED, + mappingGem.source().get() + ); + } + anyFound = true; + } + } + } + } + + public static ValueMappingOptions fromMappingGem(ValueMappingGem mapping ) { + + return new ValueMappingOptions( mapping.source().get(), mapping.target().get(), mapping.mirror(), + mapping.source().getAnnotationValue(), mapping.target().getAnnotationValue() ); + } + + private ValueMappingOptions(String source, String target, AnnotationMirror mirror, + AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue ) { + this.source = source; + this.target = target; + this.mirror = mirror; + this.sourceAnnotationValue = sourceAnnotationValue; + this.targetAnnotationValue = targetAnnotationValue; + } + + /** + * @return the name of the constant in the source. + */ + public String getSource() { + return source; + } + + /** + * @return the name of the constant in the target. + */ + public String getTarget() { + return target; + } + + public AnnotationMirror getMirror() { + return mirror; + } + + public AnnotationValue getSourceAnnotationValue() { + return sourceAnnotationValue; + } + + public AnnotationValue getTargetAnnotationValue() { + return targetAnnotationValue; + } + + public ValueMappingOptions inverse() { + ValueMappingOptions result; + if ( !(ANY_REMAINING.equals( source ) || ANY_UNMAPPED.equals( source ) || THROW_EXCEPTION.equals( target ) ) ) { + result = new ValueMappingOptions( + target, + source, + mirror, + sourceAnnotationValue, + targetAnnotationValue ); + } + else { + result = null; + } + return result; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 97 * hash + (this.source != null ? this.source.hashCode() : 0); + return hash; + } + + @Override + public boolean equals(Object obj) { + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + final ValueMappingOptions other = (ValueMappingOptions) obj; + return Objects.equals( this.source, other.source ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.java new file mode 100644 index 0000000000..87a4648d49 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/AbstractToXmlGregorianCalendar.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.ap.internal.model.source.builtin; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConstructorFragment; +import org.mapstruct.ap.internal.model.common.FieldReference; +import org.mapstruct.ap.internal.model.common.FinalField; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Base class for built-in methods for converting from a particular type to {@code XMLGregorianCalendar}. + * + * @author Sjaak Derksen + */ +public abstract class AbstractToXmlGregorianCalendar extends BuiltInMethod { + + private final Type returnType; + private final Set importTypes; + private final Type dataTypeFactoryType; + + public AbstractToXmlGregorianCalendar(TypeFactory typeFactory) { + this.returnType = typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ); + this.dataTypeFactoryType = typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_FACTORY ); + this.importTypes = asSet( + returnType, + dataTypeFactoryType, + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONFIGURATION_EXCEPTION ) + ); + } + + @Override + public Set getImportTypes() { + return importTypes; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public FieldReference getFieldReference() { + return new FinalField( dataTypeFactoryType, "datatypeFactory" ); + } + + @Override + public ConstructorFragment getConstructorFragment() { + return new NewDatatypeFactoryConstructorFragment( ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java index 9eb578e7a3..6cd1605b23 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMappingMethods.java @@ -1,29 +1,18 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; +import java.util.ArrayList; import java.util.List; +import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.util.Collections; -import org.mapstruct.ap.internal.util.JavaTimeConstants; import org.mapstruct.ap.internal.util.JaxbConstants; +import org.mapstruct.ap.internal.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; /** * Registry for all built-in methods. @@ -35,32 +24,61 @@ public class BuiltInMappingMethods { private final List builtInMethods; public BuiltInMappingMethods(TypeFactory typeFactory) { - builtInMethods = Collections.newArrayList( - new DateToXmlGregorianCalendar( typeFactory ), - new XmlGregorianCalendarToDate( typeFactory ), - new StringToXmlGregorianCalendar( typeFactory ), - new XmlGregorianCalendarToString( typeFactory ), - new CalendarToXmlGregorianCalendar( typeFactory ), - new XmlGregorianCalendarToCalendar( typeFactory ) + boolean isXmlGregorianCalendarPresent = isXmlGregorianCalendarAvailable( typeFactory ); + builtInMethods = new ArrayList<>( 21 ); + if ( isXmlGregorianCalendarPresent ) { + builtInMethods.add( new DateToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToDate( typeFactory ) ); + builtInMethods.add( new StringToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToString( typeFactory ) ); + builtInMethods.add( new CalendarToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToCalendar( typeFactory ) ); + builtInMethods.add( new ZonedDateTimeToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToLocalDate( typeFactory ) ); + builtInMethods.add( new LocalDateToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new LocalDateTimeToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToLocalDateTime( typeFactory ) ); + } - ); + if ( isJavaxJaxbAvailable( typeFactory ) ) { + Type type = typeFactory.getType( JaxbConstants.JAVAX_JAXB_ELEMENT_FQN ); + builtInMethods.add( new JaxbElemToValue( type ) ); + } - if ( isJaxbAvailable( typeFactory ) ) { - builtInMethods.add( new JaxbElemToValue( typeFactory ) ); + if ( isJakartaJaxbAvailable( typeFactory ) ) { + Type type = typeFactory.getType( JaxbConstants.JAKARTA_JAXB_ELEMENT_FQN ); + builtInMethods.add( new JaxbElemToValue( type ) ); } - if ( isJava8TimeAvailable( typeFactory ) ) { - builtInMethods.add( new ZonedDateTimeToCalendar( typeFactory ) ); - builtInMethods.add( new CalendarToZonedDateTime( typeFactory ) ); + builtInMethods.add( new ZonedDateTimeToCalendar( typeFactory ) ); + builtInMethods.add( new CalendarToZonedDateTime( typeFactory ) ); + + if ( isJodaTimeAvailable( typeFactory ) && isXmlGregorianCalendarPresent ) { + builtInMethods.add( new JodaDateTimeToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToJodaDateTime( typeFactory ) ); + builtInMethods.add( new JodaLocalDateTimeToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToJodaLocalDateTime( typeFactory ) ); + builtInMethods.add( new JodaLocalDateToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToJodaLocalDate( typeFactory ) ); + builtInMethods.add( new JodaLocalTimeToXmlGregorianCalendar( typeFactory ) ); + builtInMethods.add( new XmlGregorianCalendarToJodaLocalTime( typeFactory ) ); } } - private static boolean isJaxbAvailable(TypeFactory typeFactory) { - return typeFactory.isTypeAvailable( JaxbConstants.JAXB_ELEMENT_FQN ); + private static boolean isJavaxJaxbAvailable(TypeFactory typeFactory) { + return typeFactory.isTypeAvailable( JaxbConstants.JAVAX_JAXB_ELEMENT_FQN ); + } + + private static boolean isJakartaJaxbAvailable(TypeFactory typeFactory) { + return typeFactory.isTypeAvailable( JaxbConstants.JAKARTA_JAXB_ELEMENT_FQN ); + } + + private static boolean isXmlGregorianCalendarAvailable(TypeFactory typeFactory) { + return typeFactory.isTypeAvailable( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ); } - private static boolean isJava8TimeAvailable(TypeFactory typeFactory) { - return typeFactory.isTypeAvailable( JavaTimeConstants.ZONED_DATE_TIME_FQN ); + private static boolean isJodaTimeAvailable(TypeFactory typeFactory) { + return typeFactory.isTypeAvailable( JodaTimeConstants.DATE_TIME_FQN ); } public List getBuiltInMethods() { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java index ee5560e06d..de034d2011 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java @@ -1,45 +1,33 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; -import static org.mapstruct.ap.internal.util.Collections.first; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; - import javax.lang.model.element.ExecutableElement; -import org.mapstruct.ap.internal.conversion.SimpleConversion; import org.mapstruct.ap.internal.model.common.Accessibility; +import org.mapstruct.ap.internal.model.common.ConstructorFragment; import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.FieldReference; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.MappingMethodOptions; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.util.MapperConfiguration; +import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.util.Strings; +import static org.mapstruct.ap.internal.util.Collections.first; + /** * Represents a "built-in" mapping method which will be added as private method to the generated mapper. Built-in - * methods are used in cases where a {@link SimpleConversion} doesn't suffice, e.g. as several lines of source code or a + * methods are used in cases where a simple conversation doesn't suffice, e.g. as several lines of source code or a * try/catch block are required. * * @author Sjaak Derksen @@ -64,7 +52,7 @@ public String getName() { * @return the types used by this method for which import statements need to be generated */ public Set getImportTypes() { - return Collections.emptySet(); + return Collections.emptySet(); } /** @@ -81,21 +69,13 @@ public boolean matches(List sourceTypes, Type targetType) { Type sourceType = first( sourceTypes ); - if ( getReturnType().isAssignableTo( targetType.erasure() ) - && sourceType.erasure().isAssignableTo( getParameter().getType() ) ) { - return doTypeVarsMatch( sourceType, targetType ); - } - if ( getReturnType().getFullyQualifiedName().equals( "java.lang.Object" ) - && sourceType.erasure().isAssignableTo( getParameter().getType() ) ) { - // return type could be a type parameter T - return doTypeVarsMatch( sourceType, targetType ); - } - if ( getReturnType().isAssignableTo( targetType.erasure() ) - && getParameter().getType().getFullyQualifiedName().equals( "java.lang.Object" ) ) { - // parameter type could be a type parameter T - return doTypeVarsMatch( sourceType, targetType ); + Type returnType = getReturnType().resolveParameterToType( sourceType, getParameter().getType() ).getMatch(); + if ( returnType == null ) { + return false; } - return false; + + return returnType.isAssignableTo( targetType ) + && sourceType.erasure().isAssignableTo( getParameter().getType() ); } @Override @@ -103,6 +83,16 @@ public List getSourceParameters() { return getParameters(); } + @Override + public List getContextParameters() { + return Collections.emptyList(); + } + + @Override + public ParameterProvidedMethods getContextProvidedMethods() { + return ParameterProvidedMethods.empty(); + } + /** * {@inheritDoc} *

    @@ -141,6 +131,16 @@ public Parameter getTargetTypeParameter() { return null; } + /** + * object factory mechanism not supported for built-in methods + * + * @return false + */ + @Override + public boolean isObjectFactory() { + return false; + } + /** * the conversion context is used to format an auxiliary parameter in the method call with context specific * information such as a date format. @@ -153,6 +153,11 @@ public String getContextParameter(ConversionContext conversionContext) { return null; } + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + /** * hashCode based on class * @@ -184,14 +189,12 @@ public boolean equals(Object obj) { * * @param parameter source * @param returnType target - * @return {@code true}, iff the the type variables match + * @return {@code true}, iff the type variables match */ public boolean doTypeVarsMatch(Type parameter, Type returnType) { return true; } - - /** * There's currently only one parameter foreseen instead of a list of parameter * @@ -216,7 +219,7 @@ public Type getResultType() { @Override public List getParameterNames() { - List parameterNames = new ArrayList( getParameters().size() ); + List parameterNames = new ArrayList<>( getParameters().size() ); for ( Parameter parameter : getParameters() ) { parameterNames.add( parameter.getName() ); } @@ -226,7 +229,7 @@ public List getParameterNames() { @Override public boolean overridesMethod() { - return false; + return false; } @Override @@ -249,11 +252,6 @@ public Type getDefiningType() { return null; } - @Override - public MapperConfiguration getMapperConfiguration() { - return null; - } - @Override public boolean isLifecycleCallbackMethod() { return false; @@ -263,4 +261,24 @@ public boolean isLifecycleCallbackMethod() { public boolean isUpdateMethod() { return getMappingTargetParameter() != null; } + + @Override + public MappingMethodOptions getOptions() { + return MappingMethodOptions.empty(); + } + + public FieldReference getFieldReference() { + return null; + } + + public ConstructorFragment getConstructorFragment() { + return null; + } + + @Override + public String describe() { + // the name of the builtin method is never fully qualified, so no need to distinguish + // between verbose or not. The type knows whether it should log verbose + return getResultType().describe() + ":" + getName() + "(" + getMappingSourceType().describe() + ")"; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java index 78093c3235..3461538c41 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; @@ -22,10 +9,6 @@ import java.util.GregorianCalendar; import java.util.Set; -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.XMLGregorianCalendar; - import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; @@ -33,30 +16,29 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link Calendar} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ -public class CalendarToXmlGregorianCalendar extends BuiltInMethod { +public class CalendarToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { private final Parameter parameter; - private final Type returnType; private final Set importTypes; public CalendarToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); this.parameter = new Parameter( "cal ", typeFactory.getType( Calendar.class ) ); - this.returnType = typeFactory.getType( XMLGregorianCalendar.class ); - this.importTypes = asSet( - returnType, parameter.getType(), - typeFactory.getType( DatatypeFactory.class ), - typeFactory.getType( GregorianCalendar.class ), - typeFactory.getType( DatatypeConfigurationException.class ) + typeFactory.getType( GregorianCalendar.class ) ); } @Override public Set getImportTypes() { - return importTypes; + Set result = super.getImportTypes(); + result.addAll( this.importTypes ); + return result; } @Override @@ -64,8 +46,4 @@ public Parameter getParameter() { return parameter; } - @Override - public Type getReturnType() { - return returnType; - } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToZonedDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToZonedDateTime.java index 7371465cb7..729b65e5e9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToZonedDateTime.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/CalendarToZonedDateTime.java @@ -1,34 +1,19 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Set; -import java.util.TimeZone; 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.util.JavaTimeConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * {@link BuiltInMethod} for mapping between {@link Calendar} and {@link ZonedDateTime}. @@ -42,9 +27,9 @@ public class CalendarToZonedDateTime extends BuiltInMethod { private final Set importedTypes; CalendarToZonedDateTime(TypeFactory typeFactory) { - this.returnType = typeFactory.getType( JavaTimeConstants.ZONED_DATE_TIME_FQN ); + this.returnType = typeFactory.getType( ZonedDateTime.class ); this.parameter = new Parameter( "cal", typeFactory.getType( Calendar.class ) ); - this.importedTypes = asSet( returnType, parameter.getType(), typeFactory.getType( TimeZone.class ) ); + this.importedTypes = asSet( returnType, parameter.getType() ); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java index d027e89ad4..fb556e68a1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; @@ -22,10 +9,6 @@ import java.util.GregorianCalendar; import java.util.Set; -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.XMLGregorianCalendar; - import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; @@ -33,30 +16,29 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link Date} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ -public class DateToXmlGregorianCalendar extends BuiltInMethod { +public class DateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { private final Parameter parameter; - private final Type returnType; private final Set importTypes; public DateToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); this.parameter = new Parameter( "date", typeFactory.getType( Date.class ) ); - this.returnType = typeFactory.getType( XMLGregorianCalendar.class ); - this.importTypes = asSet( - returnType, parameter.getType(), - typeFactory.getType( GregorianCalendar.class ), - typeFactory.getType( DatatypeFactory.class ), - typeFactory.getType( DatatypeConfigurationException.class ) + typeFactory.getType( GregorianCalendar.class ) ); } @Override public Set getImportTypes() { - return importTypes; + Set result = super.getImportTypes(); + result.addAll( this.importTypes ); + return result; } @Override @@ -64,8 +46,4 @@ public Parameter getParameter() { return parameter; } - @Override - public Type getReturnType() { - return returnType; - } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.java index 0f71e84795..2a5d1639e6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.java @@ -1,46 +1,30 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; import java.util.Set; -import javax.xml.bind.JAXBElement; - 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 static org.mapstruct.ap.internal.util.Collections.asSet; /** * @author Sjaak Derksen */ -public class JaxbElemToValue extends BuiltInMethod { +class JaxbElemToValue extends BuiltInMethod { private final Parameter parameter; private final Type returnType; private final Set importTypes; - public JaxbElemToValue(TypeFactory typeFactory) { - this.parameter = new Parameter( "element", typeFactory.getType( JAXBElement.class ) ); - this.returnType = typeFactory.getType( Object.class ); - this.importTypes = asSet( typeFactory.getType( JAXBElement.class ) ); + JaxbElemToValue(Type type) { + this.parameter = new Parameter( "element", type ); + this.returnType = type.getTypeParameters().get( 0 ); + this.importTypes = asSet( parameter.getType() ); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.java new file mode 100644 index 0000000000..ef57e03175 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.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.ap.internal.model.source.builtin; + +import java.util.Set; + +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.util.JodaTimeConstants; + +/** + * A built-in method for converting from Joda {@code DateTime} to {@code XMLGregorianCalendar}. + * + * @author Sjaak Derksen + */ +public class JodaDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { + + private final Parameter parameter; + + public JodaDateTimeToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); + this.parameter = new Parameter("dt", typeFactory.getType( JodaTimeConstants.DATE_TIME_FQN ) ); + } + + @Override + public Set getImportTypes() { + Set result = super.getImportTypes(); + result.add( parameter.getType() ); + return result; + } + + @Override + public Parameter getParameter() { + return parameter; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.java new file mode 100644 index 0000000000..285ebb29fe --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.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.ap.internal.model.source.builtin; + +import java.util.Set; + +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.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from Joda {@code LocalDateTime} to {@code XMLGregorianCalendar}. + * + * @author Sjaak Derksen + */ +public class JodaLocalDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { + + private final Parameter parameter; + private final Set importTypes; + + public JodaLocalDateTimeToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); + this.parameter = new Parameter( "dt", typeFactory.getType( JodaTimeConstants.LOCAL_DATE_TIME_FQN ) ); + this.importTypes = asSet( + parameter.getType(), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ) + ); + } + + @Override + public Set getImportTypes() { + Set result = super.getImportTypes(); + result.addAll( importTypes ); + return result; + } + + @Override + public Parameter getParameter() { + return parameter; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.java new file mode 100644 index 0000000000..8163aa2fb2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.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.ap.internal.model.source.builtin; + +import java.util.Set; + +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.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from Joda {@code LocalDate} to {@code XMLGregorianCalendar}. + * + * @author Sjaak Derksen + */ +public class JodaLocalDateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { + + private final Parameter parameter; + private final Set importTypes; + + public JodaLocalDateToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); + this.parameter = new Parameter( "dt", typeFactory.getType( JodaTimeConstants.LOCAL_DATE_FQN ) ); + this.importTypes = asSet( + parameter.getType(), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ) + ); + } + + @Override + public Set getImportTypes() { + Set result = super.getImportTypes(); + result.addAll( importTypes ); + return result; + } + + @Override + public Parameter getParameter() { + return parameter; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.java new file mode 100644 index 0000000000..4fca4e0ed3 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.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.ap.internal.model.source.builtin; + +import java.util.Set; + +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.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from Joda {@code LocalTime} to {@code XMLGregorianCalendar}. + * + * @author Sjaak Derksen + */ +public class JodaLocalTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { + + private final Parameter parameter; + private final Set importTypes; + + public JodaLocalTimeToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); + this.parameter = new Parameter( "dt", typeFactory.getType( JodaTimeConstants.LOCAL_TIME_FQN ) ); + this.importTypes = asSet( + parameter.getType(), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ) + ); + } + + @Override + public Set getImportTypes() { + Set result = super.getImportTypes(); + result.addAll( importTypes ); + return result; + } + + @Override + public Parameter getParameter() { + return parameter; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.java new file mode 100644 index 0000000000..b52c7fb586 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.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.ap.internal.model.source.builtin; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoField; +import java.util.Set; + +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.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from {@link LocalDateTime} to {@code XMLGregorianCalendar}. + * + * @author Andrei Arlou + */ +public class LocalDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { + + private final Parameter parameter; + private final Set importTypes; + + public LocalDateTimeToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); + this.parameter = new Parameter( "localDateTime", typeFactory.getType( LocalDateTime.class ) ); + this.importTypes = asSet( + parameter.getType(), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), + typeFactory.getType( ChronoField.class ) + ); + } + + @Override + public Set getImportTypes() { + Set result = super.getImportTypes(); + result.addAll( importTypes ); + return result; + } + + @Override + public Parameter getParameter() { + return parameter; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.java new file mode 100644 index 0000000000..e2ec7d7696 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.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.ap.internal.model.source.builtin; + +import java.time.LocalDate; +import java.util.Set; + +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.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from {@link LocalDate} to {@code XMLGregorianCalendar}. + * + * @author Gunnar Morling + */ +public class LocalDateToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { + + private final Parameter parameter; + private final Set importTypes; + + public LocalDateToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); + this.parameter = new Parameter( "localDate", typeFactory.getType( LocalDate.class ) ); + this.importTypes = asSet( + parameter.getType(), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ) + ); + } + + @Override + public Set getImportTypes() { + Set result = super.getImportTypes(); + result.addAll( importTypes ); + return result; + } + + @Override + public Parameter getParameter() { + return parameter; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/NewDatatypeFactoryConstructorFragment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/NewDatatypeFactoryConstructorFragment.java new file mode 100644 index 0000000000..e4f00f7d57 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/NewDatatypeFactoryConstructorFragment.java @@ -0,0 +1,14 @@ +/* + * 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.model.source.builtin; + +import org.mapstruct.ap.internal.model.common.ConstructorFragment; + +public class NewDatatypeFactoryConstructorFragment implements ConstructorFragment { + + public NewDatatypeFactoryConstructorFragment() { + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java index acc71e85b0..635853d0fe 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; @@ -24,10 +11,6 @@ import java.util.GregorianCalendar; import java.util.Set; -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.XMLGregorianCalendar; - import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; @@ -36,32 +19,31 @@ import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@link String} to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ -public class StringToXmlGregorianCalendar extends BuiltInMethod { +public class StringToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { private final Parameter parameter; - private final Type returnType; private final Set importTypes; - public StringToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); this.parameter = new Parameter( "date", typeFactory.getType( String.class ) ); - this.returnType = typeFactory.getType( XMLGregorianCalendar.class ); this.importTypes = asSet( - returnType, typeFactory.getType( GregorianCalendar.class ), typeFactory.getType( SimpleDateFormat.class ), typeFactory.getType( DateFormat.class ), - typeFactory.getType( ParseException.class ), - typeFactory.getType( DatatypeFactory.class ), - typeFactory.getType( DatatypeConfigurationException.class ) + typeFactory.getType( ParseException.class ) ); } @Override public Set getImportTypes() { - return importTypes; + Set result = super.getImportTypes(); + result.addAll( importTypes ); + return result; } @Override @@ -69,11 +51,6 @@ public Parameter getParameter() { return parameter; } - @Override - public Type getReturnType() { - return returnType; - } - @Override public String getContextParameter(ConversionContext conversionContext) { return conversionContext.getDateFormat() != null ? "\"" + conversionContext.getDateFormat() + "\"" : "null"; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java index f9fd517b2c..e52b306af0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.java @@ -1,35 +1,23 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; import java.util.Calendar; import java.util.Set; -import javax.xml.datatype.XMLGregorianCalendar; - 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.util.XmlConstants; import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link Calendar}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToCalendar extends BuiltInMethod { @@ -39,7 +27,7 @@ public class XmlGregorianCalendarToCalendar extends BuiltInMethod { private final Set importTypes; public XmlGregorianCalendarToCalendar(TypeFactory typeFactory) { - this.parameter = new Parameter( "xcal", typeFactory.getType( XMLGregorianCalendar.class ) ); + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); this.returnType = typeFactory.getType( Calendar.class ); this.importTypes = asSet( returnType, parameter.getType() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java index 1a3281f66f..fd3d9a33a6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.java @@ -1,35 +1,23 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; import java.util.Date; import java.util.Set; -import javax.xml.datatype.XMLGregorianCalendar; - 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.util.XmlConstants; import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link Date}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToDate extends BuiltInMethod { @@ -39,7 +27,7 @@ public class XmlGregorianCalendarToDate extends BuiltInMethod { private final Set importTypes; public XmlGregorianCalendarToDate(TypeFactory typeFactory) { - this.parameter = new Parameter( "xcal", typeFactory.getType( XMLGregorianCalendar.class ) ); + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); this.returnType = typeFactory.getType( Date.class ); this.importTypes = asSet( returnType, parameter.getType() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.java new file mode 100644 index 0000000000..93ec3e8f96 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.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.ap.internal.model.source.builtin; + +import java.util.Calendar; +import java.util.Set; + +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.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from Joda {@code DateTime} to {@link Calendar}. + * + * @author Sjaak Derksen + */ +public class XmlGregorianCalendarToJodaDateTime extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public XmlGregorianCalendarToJodaDateTime(TypeFactory typeFactory) { + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); + this.returnType = typeFactory.getType( JodaTimeConstants.DATE_TIME_FQN ); + this.importTypes = asSet( + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), + typeFactory.getType( JodaTimeConstants.DATE_TIME_ZONE_FQN ), + returnType, + parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.java new file mode 100644 index 0000000000..9a9c1561fc --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.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.ap.internal.model.source.builtin; + +import java.util.Set; + +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.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from {@code XMLGregorianCalendar} to Joda {@code LocalDate}. + * + * @author Sjaak Derksen + */ +public class XmlGregorianCalendarToJodaLocalDate extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public XmlGregorianCalendarToJodaLocalDate(TypeFactory typeFactory) { + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); + this.returnType = typeFactory.getType( JodaTimeConstants.LOCAL_DATE_FQN ); + this.importTypes = asSet( + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), + returnType, + parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.java new file mode 100644 index 0000000000..a8d36a13cf --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.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.ap.internal.model.source.builtin; + +import java.util.Set; + +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.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from {@code XMLGregorianCalendar} to Joda {@code LocalDateTime}. + * + * @author Sjaak Derksen + */ +public class XmlGregorianCalendarToJodaLocalDateTime extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public XmlGregorianCalendarToJodaLocalDateTime(TypeFactory typeFactory) { + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); + this.returnType = typeFactory.getType( JodaTimeConstants.LOCAL_DATE_TIME_FQN ); + this.importTypes = asSet( + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), + returnType, + parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.java new file mode 100644 index 0000000000..b26450d560 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.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.ap.internal.model.source.builtin; + +import java.util.Set; + +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.util.JodaTimeConstants; +import org.mapstruct.ap.internal.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * Conversion from {@code XMLGregorianCalendar} to Joda {@code LocalTime}. + * + * @author Sjaak Derksen + */ +public class XmlGregorianCalendarToJodaLocalTime extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public XmlGregorianCalendarToJodaLocalTime(TypeFactory typeFactory) { + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); + this.returnType = typeFactory.getType( JodaTimeConstants.LOCAL_TIME_FQN ); + this.importTypes = asSet( + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), + returnType, + parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.java new file mode 100644 index 0000000000..7bcd5902a5 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.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.ap.internal.model.source.builtin; + +import java.time.LocalDate; +import java.util.Set; + +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.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link LocalDate}. + * + * @author Gunnar Morling + */ +public class XmlGregorianCalendarToLocalDate extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public XmlGregorianCalendarToLocalDate(TypeFactory typeFactory) { + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); + this.returnType = typeFactory.getType( LocalDate.class ); + this.importTypes = asSet( returnType, parameter.getType() ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.java new file mode 100644 index 0000000000..21b4c57854 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.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.model.source.builtin; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Set; + +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.util.XmlConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link LocalDateTime}. + * + * @author Andrei Arlou + */ +public class XmlGregorianCalendarToLocalDateTime extends BuiltInMethod { + + private final Parameter parameter; + private final Type returnType; + private final Set importTypes; + + public XmlGregorianCalendarToLocalDateTime(TypeFactory typeFactory) { + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); + this.returnType = typeFactory.getType( LocalDateTime.class ); + this.importTypes = asSet( + returnType, + parameter.getType(), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), + typeFactory.getType( Duration.class ) + ); + } + + @Override + public Parameter getParameter() { + return parameter; + } + + @Override + public Type getReturnType() { + return returnType; + } + + @Override + public Set getImportTypes() { + return importTypes; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java index 6cf6f7a80e..e293a9c80b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; @@ -22,16 +9,17 @@ import java.util.Date; import java.util.Set; -import javax.xml.datatype.XMLGregorianCalendar; - import org.mapstruct.ap.internal.model.common.ConversionContext; 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.util.XmlConstants; import static org.mapstruct.ap.internal.util.Collections.asSet; /** + * A built-in method for converting from {@code XMLGregorianCalendar} to {@link String}. + * * @author Sjaak Derksen */ public class XmlGregorianCalendarToString extends BuiltInMethod { @@ -41,7 +29,7 @@ public class XmlGregorianCalendarToString extends BuiltInMethod { private final Set importTypes; public XmlGregorianCalendarToString(TypeFactory typeFactory) { - this.parameter = new Parameter( "xcal", typeFactory.getType( XMLGregorianCalendar.class ) ); + this.parameter = new Parameter( "xcal", typeFactory.getType( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ) ); this.returnType = typeFactory.getType( String.class ); this.importTypes = asSet( parameter.getType(), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToCalendar.java index 01e64e1049..5a6c447908 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToCalendar.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToCalendar.java @@ -1,32 +1,20 @@ -/** - * Copyright 2012-2015 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.model.source.builtin; import java.time.ZonedDateTime; import java.util.Calendar; import java.util.Set; +import java.util.TimeZone; 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.util.Collections; -import org.mapstruct.ap.internal.util.JavaTimeConstants; + +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * {@link BuiltInMethod} for mapping between {@link Calendar} and {@link ZonedDateTime}. @@ -40,8 +28,8 @@ public class ZonedDateTimeToCalendar extends BuiltInMethod { ZonedDateTimeToCalendar(TypeFactory typeFactory) { this.returnType = typeFactory.getType( Calendar.class ); - this.parameter = new Parameter( "dateTime", typeFactory.getType( JavaTimeConstants.ZONED_DATE_TIME_FQN ) ); - this.importedTypes = Collections.asSet( returnType, parameter.getType() ); + this.parameter = new Parameter( "dateTime", typeFactory.getType( ZonedDateTime.class ) ); + this.importedTypes = asSet( returnType, parameter.getType(), typeFactory.getType( TimeZone.class ) ); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java new file mode 100644 index 0000000000..d9e39b388e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.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.ap.internal.model.source.builtin; + +import java.time.ZonedDateTime; +import java.util.GregorianCalendar; +import java.util.Set; + +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 static org.mapstruct.ap.internal.util.Collections.asSet; + +/** + * A built-in method for converting from {@link ZonedDateTime} to {@code XMLGregorianCalendar}. + * + * @author Christian Bandowski + */ +public class ZonedDateTimeToXmlGregorianCalendar extends AbstractToXmlGregorianCalendar { + + private final Parameter parameter; + private final Set importTypes; + + public ZonedDateTimeToXmlGregorianCalendar(TypeFactory typeFactory) { + super( typeFactory ); + this.parameter = new Parameter( "zdt ", typeFactory.getType( ZonedDateTime.class ) ); + + this.importTypes = asSet( + parameter.getType(), + typeFactory.getType( GregorianCalendar.class ) + ); + } + + @Override + public Set getImportTypes() { + Set result = super.getImportTypes(); + result.addAll( importTypes ); + return result; + } + + @Override + public Parameter getParameter() { + return parameter; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/package-info.java index fb90080063..040ed91f4a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

    diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/package-info.java index 19a1430bf4..f59fc2f2f3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/package-info.java @@ -1,25 +1,16 @@ -/** - * Copyright 2012-2015 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 */ /** *

    * Intermediary representation of mapping methods as retrieved from via the annotation processing API. The intermediary * representation is then processed into the mapper model representation. + * + * This intermediary presentation is primarily constructed in the + * {@link org.mapstruct.ap.internal.processor.MapperCreationProcessor} and used + * in the {@link org.mapstruct.ap.internal.processor.MapperCreationProcessor} *

    */ package org.mapstruct.ap.internal.model.source; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java index cf835dc578..b6e5ca0a53 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java @@ -1,27 +1,13 @@ -/** - * Copyright 2012-2015 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.model.source.selector; import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -42,15 +28,19 @@ public class CreateOrUpdateSelector implements MethodSelector { @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); + if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() + || criteria.isSourceParameterCheckRequired() + || criteria.isPresenceCheckRequired() ) { + return methods; + } - boolean isCreateMethod = mappingMethod.getMappingTargetParameter() == null; - List createCandidates = new ArrayList(); - List updateCandidates = new ArrayList(); - for ( T method : methods ) { - boolean isCreateCandidate = method.getMappingTargetParameter() == null; + List> createCandidates = new ArrayList<>(); + List> updateCandidates = new ArrayList<>(); + for ( SelectedMethod method : methods ) { + boolean isCreateCandidate = method.getMethod().getMappingTargetParameter() == null; if ( isCreateCandidate ) { createCandidates.add( method ); } @@ -58,10 +48,8 @@ public List getMatchingMethods(Method mappingMethod, List< updateCandidates.add( method ); } } - if ( criteria.isPreferUpdateMapping() ) { - if ( !updateCandidates.isEmpty() ) { - return updateCandidates; - } + if ( criteria.isPreferUpdateMapping() && !updateCandidates.isEmpty() ) { + return updateCandidates; } return createCandidates; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java new file mode 100644 index 0000000000..50896195f1 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java @@ -0,0 +1,46 @@ +/* + * 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.model.source.selector; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.internal.model.source.Method; + +/** + * For factory methods, the candidate list is checked if it contains a method with a source parameter which is to be + * favored compared to factory methods without a source parameter. It returns the original list of candidates in case of + * ambiguities. + * + * @author Andreas Gudian + */ +public class FactoryParameterSelector implements MethodSelector { + + @Override + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); + if ( !criteria.isObjectFactoryRequired() || methods.size() <= 1 ) { + return methods; + } + + List> sourceParamFactoryMethods = new ArrayList<>( methods.size() ); + + for ( SelectedMethod candidate : methods ) { + if ( !candidate.getMethod().getSourceParameters().isEmpty() ) { + sourceParamFactoryMethods.add( candidate ); + } + } + + if ( sourceParamFactoryMethods.size() == 1 ) { + // there is exactly one candidate with source params, so favor that one. + return sourceParamFactoryMethods; + } + + // let the caller produce an ambiguity error referencing all possibly matching methods + return methods; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java index 0356d4330a..7b56564ba9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.source.selector; @@ -35,24 +22,20 @@ public class InheritanceSelector implements MethodSelector { @Override - public List getMatchingMethods( - Method mappingMethod, - List methods, - Type sourceType, - Type targetType, - SelectionCriteria criteria - ) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + Type sourceType = context.getSourceType(); if ( sourceType == null ) { return methods; } - List candidatesWithBestMatchingSourceType = new ArrayList(); + List> candidatesWithBestMatchingSourceType = new ArrayList<>(); int bestMatchingSourceTypeDistance = Integer.MAX_VALUE; - // find the methods with the minimum distance regarding getParameter getParameter type - for ( T method : methods ) { - Parameter singleSourceParam = first( method.getSourceParameters() ); + // Find methods with the minimum inheritance distance from the source parameter type + for ( SelectedMethod method : methods ) { + Parameter singleSourceParam = first( method.getMethod().getSourceParameters() ); int sourceTypeDistance = sourceType.distanceTo( singleSourceParam.getType() ); bestMatchingSourceTypeDistance = @@ -66,17 +49,17 @@ public List getMatchingMethods( return candidatesWithBestMatchingSourceType; } - private int addToCandidateListIfMinimal(List candidatesWithBestMathingType, - int bestMatchingTypeDistance, T method, + private int addToCandidateListIfMinimal(List> candidatesWithBestMatchingType, + int bestMatchingTypeDistance, SelectedMethod method, int currentTypeDistance) { if ( currentTypeDistance == bestMatchingTypeDistance ) { - candidatesWithBestMathingType.add( method ); + candidatesWithBestMatchingType.add( method ); } else if ( currentTypeDistance < bestMatchingTypeDistance ) { bestMatchingTypeDistance = currentTypeDistance; - candidatesWithBestMathingType.clear(); - candidatesWithBestMathingType.add( method ); + candidatesWithBestMatchingType.clear(); + candidatesWithBestMatchingType.add( method ); } return bestMatchingTypeDistance; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java new file mode 100644 index 0000000000..d5595ef6b7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JakartaXmlElementDeclSelector.java @@ -0,0 +1,48 @@ +/* + * 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.model.source.selector; + +import javax.lang.model.element.Element; + +import org.mapstruct.ap.internal.gem.jakarta.XmlElementDeclGem; +import org.mapstruct.ap.internal.gem.jakarta.XmlElementRefGem; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * The concrete implementation of the {@link XmlElementDeclSelector} that + * works with {@link jakarta.xml.bind.annotation.XmlElementRef} and + * {@link jakarta.xml.bind.annotation.XmlElementDecl}. + * + * @author Iaroslav Bogdanchikov + */ +class JakartaXmlElementDeclSelector extends XmlElementDeclSelector { + + JakartaXmlElementDeclSelector(TypeUtils typeUtils) { + super( typeUtils ); + } + + @Override + XmlElementDeclInfo getXmlElementDeclInfo(Element element) { + XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element ); + + if ( gem == null ) { + return null; + } + + return new XmlElementDeclInfo( gem.name().get(), gem.scope().get() ); + } + + @Override + XmlElementRefInfo getXmlElementRefInfo(Element element) { + XmlElementRefGem gem = XmlElementRefGem.instanceOn( element ); + + if ( gem == null ) { + return null; + } + + return new XmlElementRefInfo( gem.name().get(), gem.type().get() ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java new file mode 100644 index 0000000000..912da3e696 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/JavaxXmlElementDeclSelector.java @@ -0,0 +1,48 @@ +/* + * 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.model.source.selector; + +import javax.lang.model.element.Element; + +import org.mapstruct.ap.internal.gem.XmlElementDeclGem; +import org.mapstruct.ap.internal.gem.XmlElementRefGem; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * The concrete implementation of the {@link XmlElementDeclSelector} that + * works with {@link javax.xml.bind.annotation.XmlElementRef} and + * {@link javax.xml.bind.annotation.XmlElementDecl}. + * + * @author Iaroslav Bogdanchikov + */ +class JavaxXmlElementDeclSelector extends XmlElementDeclSelector { + + JavaxXmlElementDeclSelector(TypeUtils typeUtils) { + super( typeUtils ); + } + + @Override + XmlElementDeclInfo getXmlElementDeclInfo(Element element) { + XmlElementDeclGem gem = XmlElementDeclGem.instanceOn( element ); + + if ( gem == null ) { + return null; + } + + return new XmlElementDeclInfo( gem.name().get(), gem.scope().get() ); + } + + @Override + XmlElementRefInfo getXmlElementRefInfo(Element element) { + XmlElementRefGem gem = XmlElementRefGem.instanceOn( element ); + + if ( gem == null ) { + return null; + } + + return new XmlElementRefInfo( gem.name().get(), gem.type().get() ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java new file mode 100644 index 0000000000..7ce23af186 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/LifecycleOverloadDeduplicateSelector.java @@ -0,0 +1,129 @@ +/* + * 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.model.source.selector; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * Selector for deduplicating overloaded lifecycle callback methods + * whose parameter signatures differ only by type hierarchy. + *

    + * In the context of lifecycle callback method selection + * (such as @BeforeMapping or @AfterMapping), it is possible to have multiple overloaded methods + * whose parameter lists are structurally identical except for the specific types, + * where those types are related by inheritance (e.g., one parameter is a superclass or subclass of another). + *

    + * This selector groups such methods by their effective parameter signature + * (ignoring differences only in type hierarchy), and, within each group, + * retains only the method whose parameter types have the closest inheritance distance + * to the actual invocation types. + * This ensures that, for each group of nearly identical overloads, + * only the most specific and appropriate method is selected. + *

    + * Example (see Issue3849Test): + * + *

    {@code
    + * @AfterMapping
    + * default void afterMapping(Parent source, @MappingTarget ParentDto target) { ... }
    + * @AfterMapping
    + * default void afterMapping(Parent source, @MappingTarget ChildDto target) { ... }
    + * }
    + * When mapping a Child to a ChildDto, + * only the method with ChildDto is selected, even though both methods match by signature + * except for the target type's inheritance relationship. + */ +public class LifecycleOverloadDeduplicateSelector implements MethodSelector { + @Override + public List> getMatchingMethods(List> methods, + SelectionContext context) { + if ( !context.getSelectionCriteria().isLifecycleCallbackRequired() || methods.size() <= 1 ) { + return methods; + } + Collection>> methodSignatureGroups = + methods.stream() + .collect( Collectors.groupingBy( + LifecycleOverloadDeduplicateSelector::buildSignatureKey, + LinkedHashMap::new, + Collectors.toList() + ) ) + .values(); + List> deduplicatedMethods = new ArrayList<>( methods.size() ); + for ( List> signatureGroup : methodSignatureGroups ) { + if ( signatureGroup.size() == 1 ) { + deduplicatedMethods.add( signatureGroup.get( 0 ) ); + continue; + } + SelectedMethod bestInheritanceMethod = signatureGroup.get( 0 ); + for ( int i = 1; i < signatureGroup.size(); i++ ) { + SelectedMethod candidateMethod = signatureGroup.get( i ); + if ( isInheritanceBetter( candidateMethod, bestInheritanceMethod ) ) { + bestInheritanceMethod = candidateMethod; + } + } + deduplicatedMethods.add( bestInheritanceMethod ); + } + return deduplicatedMethods; + } + + /** + * Builds a grouping key for a method based on its defining type, + * method name, and a detailed breakdown of each parameter binding. + *

    + * The key consists of: + *

      + *
    • The type that defines the method
    • + *
    • The method name
    • + *
    • parameter bindings
    • + *
    + * This ensures that methods are grouped together only if all these aspects match, + * except for differences in type hierarchy, which are handled separately. + */ + private static List buildSignatureKey(SelectedMethod method) { + List key = new ArrayList<>(); + key.add( method.getMethod().getDefiningType() ); + key.add( method.getMethod().getName() ); + for ( ParameterBinding binding : method.getParameterBindings() ) { + key.add( binding.getType() ); + key.add( binding.getVariableName() ); + } + return key; + } + + /** + * Compare the inheritance distance of parameters between two methods to determine if candidateMethod is better. + * Compares parameters in order, returns as soon as a better one is found. + */ + private boolean isInheritanceBetter(SelectedMethod candidateMethod, + SelectedMethod currentBestMethod) { + List candidateBindings = candidateMethod.getParameterBindings(); + List bestBindings = currentBestMethod.getParameterBindings(); + List candidateParams = candidateMethod.getMethod().getParameters(); + List bestParams = currentBestMethod.getMethod().getParameters(); + int paramCount = candidateBindings.size(); + + for ( int i = 0; i < paramCount; i++ ) { + int candidateDistance = candidateBindings.get( i ) + .getType() + .distanceTo( candidateParams.get( i ).getType() ); + int bestDistance = bestBindings.get( i ).getType().distanceTo( bestParams.get( i ).getType() ); + if ( candidateDistance < bestDistance ) { + return true; + } + else if ( candidateDistance > bestDistance ) { + return false; + } + } + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java new file mode 100644 index 0000000000..691199b49d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.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.ap.internal.model.source.selector; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * Selects those methods from the given input set which match for the requested family of methods: factory methods, + * lifecycle callback methods, or any other mapping methods. + * + * @author Remo Meier + */ +public class MethodFamilySelector implements MethodSelector { + + @Override + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); + + List> result = new ArrayList<>( methods.size() ); + for ( SelectedMethod method : methods ) { + if ( criteria.isPresenceCheckRequired() ) { + if ( method.getMethod() + .getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) { + result.add( method ); + } + } + else if ( criteria.isSourceParameterCheckRequired() ) { + if ( method.getMethod() + .getConditionOptions() + .isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) { + result.add( method ); + } + } + else if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired() + && method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired() + ) { + + result.add( method ); + } + } + return result; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java index c74ef5ba59..375d5a9bf8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java @@ -1,26 +1,12 @@ -/** - * Copyright 2012-2015 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.model.source.selector; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -30,20 +16,16 @@ * * @author Sjaak Derksen */ -public interface MethodSelector { +interface MethodSelector { /** * Selects those methods which match the given types and other criteria * * @param either SourceMethod or BuiltInMethod - * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out - * @param methods list of available methods - * @param sourceType parameter type that should be matched - * @param targetType return type that should be matched - * @param criteria criteria used in the selection process - * + * @param candidates list of available methods + * @param context the context for the matching * @return list of methods that passes the matching process */ - List getMatchingMethods(Method mappingMethod, List methods, Type sourceType, - Type targetType, SelectionCriteria criteria); + List> getMatchingMethods(List> candidates, + SelectionContext context); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index 50c9c8385d..774f25a8c3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.source.selector; @@ -22,49 +9,63 @@ import java.util.Arrays; import java.util.List; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.TypeUtils; /** * Applies all known {@link MethodSelector}s in order. * * @author Sjaak Derksen */ -public class MethodSelectors implements MethodSelector { +public class MethodSelectors { private final List selectors; - public MethodSelectors(Types typeUtils, Elements elementUtils, TypeFactory typeFactory) { - selectors = - Arrays.asList( - new TypeSelector(), - new QualifierSelector( typeUtils, elementUtils ), - new TargetTypeSelector( typeUtils, elementUtils ), - new XmlElementDeclSelector( typeUtils ), - new InheritanceSelector(), - new CreateOrUpdateSelector() - ); + public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, + FormattingMessager messager, Options options) { + List selectorList = new ArrayList<>( Arrays.asList( + new MethodFamilySelector(), + new TypeSelector( messager ), + new QualifierSelector( typeUtils, elementUtils ), + new TargetTypeSelector( typeUtils ), + new JavaxXmlElementDeclSelector( typeUtils ), + new JakartaXmlElementDeclSelector( typeUtils ), + new InheritanceSelector() + ) ); + if ( options != null && !options.isDisableLifecycleOverloadDeduplicateSelector() ) { + selectorList.add( new LifecycleOverloadDeduplicateSelector() ); + } + + selectorList.addAll( Arrays.asList( + new CreateOrUpdateSelector(), + new SourceRhsSelector(), + new FactoryParameterSelector(), + new MostSpecificResultTypeSelector() + ) ); + this.selectors = selectorList; } - @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + /** + * Selects those methods which match the given types and other criteria + * + * @param either SourceMethod or BuiltInMethod + * @param methods list of available methods + * @param context the selection context that should be used in the matching process + * @return list of methods that passes the matching process + */ + public List> getMatchingMethods(List methods, + SelectionContext context) { - List candidates = new ArrayList( methods ); + List> candidates = new ArrayList<>( methods.size() ); + for ( T method : methods ) { + candidates.add( new SelectedMethod<>( method ) ); + } for ( MethodSelector selector : selectors ) { - candidates = selector.getMatchingMethods( - mappingMethod, - candidates, - sourceType, - targetType, - criteria - ); + candidates = selector.getMatchingMethods( candidates, context ); } return candidates; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java new file mode 100644 index 0000000000..88e302eba0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MostSpecificResultTypeSelector.java @@ -0,0 +1,47 @@ +/* + * 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.model.source.selector; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * A {@link MethodSelector} that selects the most specific result type. + * + * @author Filip Hrisafov + */ +public class MostSpecificResultTypeSelector implements MethodSelector { + + @Override + public List> getMatchingMethods(List> candidates, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); + Type mappingTargetType = context.getMappingTargetType(); + if ( candidates.size() < 2 || !criteria.isForMapping() || criteria.getQualifyingResultType() != null ) { + return candidates; + } + + List> result = new ArrayList<>(); + + for ( SelectedMethod candidate : candidates ) { + if ( candidate.getMethod() + .getResultType() + .getBoxedEquivalent() + .equals( mappingTargetType.getBoxedEquivalent() ) ) { + // If the result type is the same as the target type + // then this candidate has the most specific match and should be used + result.add( candidate ); + } + } + + + // If not most specific types were found then return the current candidates + return result.isEmpty() ? candidates : result; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java index be099443e8..744c03d77c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.model.source.selector; @@ -22,16 +9,17 @@ import java.util.HashSet; import java.util.List; import java.util.Set; - import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; +import org.mapstruct.ap.internal.gem.NamedGem; +import org.mapstruct.ap.internal.gem.QualifierGem; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.prism.QualifierPrism; +import org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.TypeUtils; /** * This selector selects a best match based on qualifier annotations. @@ -52,26 +40,46 @@ */ public class QualifierSelector implements MethodSelector { - private final Types typeUtils; - + private final TypeUtils typeUtils; + private final TypeMirror namedAnnotationTypeMirror; - public QualifierSelector( Types typeUtils, Elements elementUtils ) { + public QualifierSelector(TypeUtils typeUtils, ElementUtils elementUtils ) { this.typeUtils = typeUtils; + namedAnnotationTypeMirror = elementUtils.getTypeElement( "org.mapstruct.Named" ).asType(); } @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { - - List qualifiers = criteria.getQualifiers(); - if ( qualifiers == null || qualifiers.isEmpty() ) { - // remove the method marked as qualifier from the list - List nonQualiferAnnotatedMethods = new ArrayList(); - for ( T candidate : methods ) { - - if ( candidate instanceof SourceMethod ) { - Set qualifierAnnotations = getQualifierAnnotations( candidate ); + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); + + int numberOfQualifiersToMatch = 0; + + // Define some local collections and make sure that they are defined. + List qualifierTypes = new ArrayList<>(); + if ( criteria.getQualifiers() != null ) { + qualifierTypes.addAll( criteria.getQualifiers() ); + numberOfQualifiersToMatch += criteria.getQualifiers().size(); + } + List qualfiedByNames = new ArrayList<>(); + if ( criteria.getQualifiedByNames() != null ) { + qualfiedByNames.addAll( criteria.getQualifiedByNames() ); + numberOfQualifiersToMatch += criteria.getQualifiedByNames().size(); + } + + // add the mapstruct @Named annotation as annotation to look for + if ( !qualfiedByNames.isEmpty() ) { + qualifierTypes.add( namedAnnotationTypeMirror ); + } + + // Check there are qualfiers for this mapping: Mapping#qualifier or Mapping#qualfiedByName + if ( qualifierTypes.isEmpty() ) { + // When no qualifiers, disqualify all methods marked with a qualifier by removing them from the candidates + List> nonQualiferAnnotatedMethods = new ArrayList<>( methods.size() ); + for ( SelectedMethod candidate : methods ) { + + if ( candidate.getMethod() instanceof SourceMethod ) { + Set qualifierAnnotations = getQualifierAnnotationMirrors( candidate.getMethod() ); if ( qualifierAnnotations.isEmpty() ) { nonQualiferAnnotatedMethods.add( candidate ); } @@ -84,46 +92,58 @@ public List getMatchingMethods(Method mappingMethod, List< return nonQualiferAnnotatedMethods; } else { + // Check all methods marked with qualfier (or methods in Mappers marked wiht a qualfier) for matches. + List> matches = new ArrayList<>( methods.size() ); + for ( SelectedMethod candidate : methods ) { - List matches = new ArrayList(); - for ( T candidate : methods ) { - - if ( !( candidate instanceof SourceMethod ) ) { + if ( !( candidate.getMethod() instanceof SourceMethod ) ) { continue; } // retrieve annotations - Set qualifierAnnotations = getQualifierAnnotations( candidate ); + Set qualifierAnnotationMirrors = + getQualifierAnnotationMirrors( candidate.getMethod() ); // now count if all qualifiers are matched int matchingQualifierCounter = 0; - for ( TypeMirror qualifier : qualifiers ) { - for ( TypeMirror annotationType : qualifierAnnotations ) { - if ( typeUtils.isSameType( qualifier, annotationType ) ) { - matchingQualifierCounter++; + for ( AnnotationMirror qualifierAnnotationMirror : qualifierAnnotationMirrors ) { + for ( TypeMirror qualifierType : qualifierTypes ) { + + // get the type of the annotation positionHint. + DeclaredType qualifierAnnotationType = qualifierAnnotationMirror.getAnnotationType(); + if ( typeUtils.isSameType( qualifierType, qualifierAnnotationType ) ) { + // Match! we have an annotation which has the @Qualifer marker ( could be @Named as well ) + if ( typeUtils.isSameType( qualifierAnnotationType, namedAnnotationTypeMirror ) ) { + // Match! its an @Named, so do the additional check on name. + NamedGem named = NamedGem.instanceOn( qualifierAnnotationMirror ); + if ( named.value().hasValue() && qualfiedByNames.contains( named.value().get() ) ) { + // Match! its an @Name and the value matches as well. Oh boy. + matchingQualifierCounter++; + } + } + else { + // Match! its a self declared qualifer annoation (marked with @Qualifier) + matchingQualifierCounter++; + } break; } + } } - if ( matchingQualifierCounter == qualifiers.size() ) { - // all qualifiers are matched with a qualifying annotation, add candidate + if ( matchingQualifierCounter == numberOfQualifiersToMatch ) { + // Only if all qualifiers are matched with a qualifying annotation, add candidate matches.add( candidate ); } } - if ( !matches.isEmpty() ) { - return matches; - } - else { - return methods; - } + return matches; } } - private Set getQualifierAnnotations( Method candidate ) { + private Set getQualifierAnnotationMirrors( Method candidate ) { // retrieve annotations - Set qualiferAnnotations = new HashSet(); + Set qualiferAnnotations = new HashSet<>(); // first from the method itself SourceMethod candidateSM = (SourceMethod) candidate; @@ -144,12 +164,11 @@ private Set getQualifierAnnotations( Method candidate ) { return qualiferAnnotations; } - private void addOnlyWhenQualifier( Set annotationSet, AnnotationMirror candidate ) { + private void addOnlyWhenQualifier( Set annotationSet, AnnotationMirror candidate ) { // only add the candidate annotation when the candidate itself has the annotation 'Qualifier' - if ( QualifierPrism.getInstanceOn( candidate.getAnnotationType().asElement() ) != null ) { - annotationSet.add( candidate.getAnnotationType() ); + if ( QualifierGem.instanceOn( candidate.getAnnotationType().asElement() ) != null ) { + annotationSet.add( candidate ); } } } - diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java new file mode 100644 index 0000000000..1702a8d4d6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.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.ap.internal.model.source.selector; + +import java.util.List; +import java.util.Objects; + +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * A selected method with additional metadata that might be required for further usage of the selected method. + * + * @param the type of the method + * + * @author Andreas Gudian + */ +public class SelectedMethod { + private T method; + private List parameterBindings; + + public SelectedMethod(T method) { + this.method = method; + } + + public T getMethod() { + return method; + } + + public List getParameterBindings() { + return parameterBindings; + } + + public void setParameterBindings(List parameterBindings) { + this.parameterBindings = parameterBindings; + } + + @Override + public String toString() { + return method.toString(); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SelectedMethod that = (SelectedMethod) o; + return method.equals( that.method ); + } + + @Override + public int hashCode() { + return Objects.hash( method ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java new file mode 100644 index 0000000000..79351ae868 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -0,0 +1,302 @@ +/* + * 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.model.source.selector; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.common.SourceRHS; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; + +/** + * Context passed to the selectors to get the information they need. + * + * @author Filip Hrisafov + */ +public class SelectionContext { + + private final Type sourceType; + private final SelectionCriteria selectionCriteria; + private final Method mappingMethod; + private final Type mappingTargetType; + private final Type returnType; + private final Supplier> parameterBindingsProvider; + private List parameterBindings; + + private SelectionContext(Type sourceType, SelectionCriteria selectionCriteria, Method mappingMethod, + Type mappingTargetType, Type returnType, + Supplier> parameterBindingsProvider) { + this.sourceType = sourceType; + this.selectionCriteria = selectionCriteria; + this.mappingMethod = mappingMethod; + this.mappingTargetType = mappingTargetType; + this.returnType = returnType; + this.parameterBindingsProvider = parameterBindingsProvider; + } + + /** + * @return the source type that should be matched + */ + public Type getSourceType() { + return sourceType; + } + + /** + * @return the criteria used in the selection process + */ + public SelectionCriteria getSelectionCriteria() { + return selectionCriteria; + } + + /** + * @return the mapping target type that should be matched + */ + public Type getMappingTargetType() { + return mappingTargetType; + } + + /** + * @return the return type that should be matched + */ + public Type getReturnType() { + return returnType; + } + + /** + * @return the available parameter bindings for the matching + */ + public List getAvailableParameterBindings() { + if ( this.parameterBindings == null ) { + this.parameterBindings = this.parameterBindingsProvider.get(); + } + return parameterBindings; + } + + /** + * @return the mapping method, defined in Mapper for which this selection is carried out + */ + public Method getMappingMethod() { + return mappingMethod; + } + + public static SelectionContext forMappingMethods(Method mappingMethod, Type source, Type target, + SelectionCriteria criteria, TypeFactory typeFactory) { + return new SelectionContext( + source, + criteria, + mappingMethod, + target, + target, + () -> getAvailableParameterBindingsFromSourceType( + source, + target, + mappingMethod, + typeFactory + ) + ); + } + + public static SelectionContext forLifecycleMethods(Method mappingMethod, Type targetType, + SelectionParameters selectionParameters, + TypeFactory typeFactory, + Supplier> additionalParameterBindingsProvider) { + SelectionCriteria criteria = SelectionCriteria.forLifecycleMethods( selectionParameters ); + return new SelectionContext( + null, + criteria, + mappingMethod, + targetType, + mappingMethod.getResultType(), + () -> { + List parameterBindings = getAvailableParameterBindingsFromMethod( + mappingMethod, + targetType, + criteria.getSourceRHS(), + typeFactory + ); + parameterBindings.addAll( additionalParameterBindingsProvider.get() ); + return parameterBindings; + } + ); + } + + public static SelectionContext forFactoryMethods(Method mappingMethod, Type alternativeTarget, + SelectionParameters selectionParameters, + TypeFactory typeFactory) { + SelectionCriteria criteria = SelectionCriteria.forFactoryMethods( selectionParameters ); + return new SelectionContext( + null, + criteria, + mappingMethod, + alternativeTarget, + alternativeTarget, + () -> getAvailableParameterBindingsFromMethod( + mappingMethod, + alternativeTarget, + criteria.getSourceRHS(), + typeFactory + ) + ); + } + + public static SelectionContext forPresenceCheckMethods(Method mappingMethod, + SelectionParameters selectionParameters, + TypeFactory typeFactory) { + SelectionCriteria criteria = SelectionCriteria.forPresenceCheckMethods( selectionParameters ); + Type booleanType = typeFactory.getType( Boolean.class ); + return new SelectionContext( + null, + criteria, + mappingMethod, + booleanType, + booleanType, + () -> getAvailableParameterBindingsFromMethod( + mappingMethod, + booleanType, + criteria.getSourceRHS(), + typeFactory + ) + ); + } + + public static SelectionContext forSourceParameterPresenceCheckMethods(Method mappingMethod, + SelectionParameters selectionParameters, + Parameter sourceParameter, + TypeFactory typeFactory) { + SelectionCriteria criteria = SelectionCriteria.forSourceParameterCheckMethods( selectionParameters ); + Type booleanType = typeFactory.getType( Boolean.class ); + return new SelectionContext( + null, + criteria, + mappingMethod, + booleanType, + booleanType, + () -> getParameterBindingsForSourceParameterPresenceCheck( + mappingMethod, + booleanType, + sourceParameter, + typeFactory + ) + ); + } + + private static List getParameterBindingsForSourceParameterPresenceCheck(Method method, + Type targetType, + Parameter sourceParameter, + TypeFactory typeFactory) { + + List availableParams = new ArrayList<>( method.getParameters().size() + 3 ); + + availableParams.add( ParameterBinding.fromParameter( sourceParameter ) ); + availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); + for ( Parameter parameter : method.getParameters() ) { + if ( !parameter.isSourceParameter( ) ) { + availableParams.add( ParameterBinding.fromParameter( parameter ) ); + } + } + + return availableParams; + } + + private static List getAvailableParameterBindingsFromMethod(Method method, Type targetType, + SourceRHS sourceRHS, + TypeFactory typeFactory) { + List availableParams = new ArrayList<>( method.getParameters().size() + 3 ); + + if ( sourceRHS != null ) { + availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); + availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) ); + addSourcePropertyNameBindings( availableParams, sourceRHS.getSourceType(), typeFactory ); + } + else { + availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); + } + + addTargetRelevantBindings( availableParams, targetType, typeFactory ); + + return availableParams; + } + + private static List getAvailableParameterBindingsFromSourceType(Type sourceType, + Type targetType, + Method mappingMethod, + TypeFactory typeFactory) { + + List availableParams = new ArrayList<>(); + + availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); + addSourcePropertyNameBindings( availableParams, sourceType, typeFactory ); + + for ( Parameter param : mappingMethod.getParameters() ) { + if ( param.isMappingContext() ) { + availableParams.add( ParameterBinding.fromParameter( param ) ); + } + } + + addTargetRelevantBindings( availableParams, targetType, typeFactory ); + + return availableParams; + } + + private static void addSourcePropertyNameBindings(List availableParams, Type sourceType, + TypeFactory typeFactory) { + + boolean sourcePropertyNameAvailable = false; + for ( ParameterBinding pb : availableParams ) { + if ( pb.isSourcePropertyName() ) { + sourcePropertyNameAvailable = true; + break; + } + } + if ( !sourcePropertyNameAvailable ) { + availableParams.add( ParameterBinding.forSourcePropertyNameBinding( typeFactory.getType( String.class ) ) ); + } + + } + + /** + * Adds default parameter bindings for the mapping-target and target-type if not already available. + * + * @param availableParams Already available params, new entries will be added to this list + * @param targetType Target type + */ + private static void addTargetRelevantBindings(List availableParams, Type targetType, + TypeFactory typeFactory) { + boolean mappingTargetAvailable = false; + boolean targetTypeAvailable = false; + boolean targetPropertyNameAvailable = false; + + // search available parameter bindings if mapping-target and/or target-type is available + for ( ParameterBinding pb : availableParams ) { + if ( pb.isMappingTarget() ) { + mappingTargetAvailable = true; + } + else if ( pb.isTargetType() ) { + targetTypeAvailable = true; + } + else if ( pb.isTargetPropertyName() ) { + targetPropertyNameAvailable = true; + } + } + + if ( !mappingTargetAvailable ) { + availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) ); + } + if ( !targetTypeAvailable ) { + availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); + } + if ( !targetPropertyNameAvailable ) { + availableParams.add( ParameterBinding.forTargetPropertyNameBinding( typeFactory.getType( String.class ) ) ); + } + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index 9520d4ad04..45e11fe6ea 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -1,26 +1,18 @@ -/** - * Copyright 2012-2015 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.model.source.selector; +import java.util.Collections; import java.util.List; import javax.lang.model.type.TypeMirror; +import org.mapstruct.ap.internal.model.common.SourceRHS; +import org.mapstruct.ap.internal.model.source.MappingControl; +import org.mapstruct.ap.internal.model.source.SelectionParameters; + /** * This class groups the selection criteria in one class * @@ -28,21 +20,79 @@ */ public class SelectionCriteria { - private final List qualifiers; + private final QualifyingInfo qualifyingInfo; private final String targetPropertyName; - private final TypeMirror qualifyingResultType; - private boolean preferUpdateMapping; + private final SourceRHS sourceRHS; + private boolean ignoreQualifiers = false; + private Type type; + private final MappingControl mappingControl; - public SelectionCriteria(List qualifiers, String targetPropertyName, TypeMirror qualifyingResultType, - boolean preferUpdateMapping ) { - this.qualifiers = qualifiers; + public SelectionCriteria(SelectionParameters selectionParameters, MappingControl mappingControl, + String targetPropertyName, Type type) { + this( + QualifyingInfo.fromSelectionParameters( selectionParameters ), + selectionParameters != null ? selectionParameters.getSourceRHS() : null, + mappingControl, + targetPropertyName, + type + ); + } + + private SelectionCriteria(QualifyingInfo qualifyingInfo, SourceRHS sourceRHS, MappingControl mappingControl, + String targetPropertyName, Type type) { + this.qualifyingInfo = qualifyingInfo; this.targetPropertyName = targetPropertyName; - this.qualifyingResultType = qualifyingResultType; - this.preferUpdateMapping = preferUpdateMapping; + this.sourceRHS = sourceRHS; + this.type = type; + this.mappingControl = mappingControl; + } + + /** + * + * @return {@code true} if only mapping methods should be selected + */ + public boolean isForMapping() { + return type == null || type == Type.PREFER_UPDATE_MAPPING; + } + + /** + * @return true if factory methods should be selected, false otherwise. + */ + public boolean isObjectFactoryRequired() { + return type == Type.OBJECT_FACTORY; + } + + /** + * @return true if lifecycle callback methods should be selected, false otherwise. + */ + public boolean isLifecycleCallbackRequired() { + return type == Type.LIFECYCLE_CALLBACK; + } + + /** + * @return {@code true} if presence check methods should be selected, {@code false} otherwise + */ + public boolean isPresenceCheckRequired() { + return type == Type.PRESENCE_CHECK; + } + + /** + * @return {@code true} if source parameter check methods should be selected, {@code false} otherwise + */ + public boolean isSourceParameterCheckRequired() { + return type == Type.SOURCE_PARAMETER_CHECK; + } + + public void setIgnoreQualifiers(boolean ignoreQualifiers) { + this.ignoreQualifiers = ignoreQualifiers; } public List getQualifiers() { - return qualifiers; + return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiers(); + } + + public List getQualifiedByNames() { + return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiedByNames(); } public String getTargetPropertyName() { @@ -50,15 +100,143 @@ public String getTargetPropertyName() { } public TypeMirror getQualifyingResultType() { - return qualifyingResultType; + return qualifyingInfo.qualifyingResultType(); } public boolean isPreferUpdateMapping() { - return preferUpdateMapping; + return type == Type.PREFER_UPDATE_MAPPING; + } + + public SourceRHS getSourceRHS() { + return sourceRHS; } public void setPreferUpdateMapping(boolean preferUpdateMapping) { - this.preferUpdateMapping = preferUpdateMapping; + this.type = preferUpdateMapping ? Type.PREFER_UPDATE_MAPPING : null; + } + + public boolean hasQualfiers() { + return !qualifyingInfo.qualifiedByNames().isEmpty() || !qualifyingInfo.qualifiers().isEmpty(); + } + + public boolean isAllowDirect() { + return mappingControl == null || mappingControl.allowDirect(); + } + + public boolean isAllowConversion() { + return mappingControl == null || mappingControl.allowTypeConversion(); + } + + public boolean isAllowMappingMethod() { + return mappingControl == null || mappingControl.allowMappingMethod(); + } + + public boolean isAllow2Steps() { + return mappingControl == null || mappingControl.allowBy2Steps(); + } + + public boolean isSelfAllowed() { + return type != Type.SELF_NOT_ALLOWED; + } + + public static SelectionCriteria forMappingMethods(SelectionParameters selectionParameters, + MappingControl mappingControl, + String targetPropertyName, boolean preferUpdateMapping) { + + return new SelectionCriteria( + selectionParameters, + mappingControl, + targetPropertyName, + preferUpdateMapping ? Type.PREFER_UPDATE_MAPPING : null + ); } + public static SelectionCriteria forFactoryMethods(SelectionParameters selectionParameters) { + return new SelectionCriteria( selectionParameters, null, null, Type.OBJECT_FACTORY ); + } + + public static SelectionCriteria forLifecycleMethods(SelectionParameters selectionParameters) { + return new SelectionCriteria( selectionParameters, null, null, Type.LIFECYCLE_CALLBACK ); + } + + public static SelectionCriteria forPresenceCheckMethods(SelectionParameters selectionParameters) { + SourceRHS sourceRHS = selectionParameters.getSourceRHS(); + Type type; + QualifyingInfo qualifyingInfo = new QualifyingInfo( + selectionParameters.getConditionQualifiers(), + selectionParameters.getConditionQualifyingNames(), + selectionParameters.getResultType() + ); + if ( sourceRHS != null && sourceRHS.isSourceReferenceParameter() ) { + // If the source reference is for a source parameter, + // then the presence check should be for the source parameter + type = Type.SOURCE_PARAMETER_CHECK; + } + else { + type = Type.PRESENCE_CHECK; + } + return new SelectionCriteria( qualifyingInfo, sourceRHS, null, null, type ); + } + + public static SelectionCriteria forSourceParameterCheckMethods(SelectionParameters selectionParameters) { + return new SelectionCriteria( selectionParameters, null, null, Type.SOURCE_PARAMETER_CHECK ); + } + + public static SelectionCriteria forSubclassMappingMethods(SelectionParameters selectionParameters, + MappingControl mappingControl) { + return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED ); + } + + private static class QualifyingInfo { + + private static final QualifyingInfo EMPTY = new QualifyingInfo( + Collections.emptyList(), + Collections.emptyList(), + null + ); + + private final List qualifiers; + private final List qualifiedByNames; + private final TypeMirror qualifyingResultType; + + private QualifyingInfo(List qualifiers, List qualifiedByNames, + TypeMirror qualifyingResultType) { + this.qualifiers = qualifiers; + this.qualifiedByNames = qualifiedByNames; + this.qualifyingResultType = qualifyingResultType; + } + + public List qualifiers() { + return qualifiers; + } + + public List qualifiedByNames() { + return qualifiedByNames; + } + + public TypeMirror qualifyingResultType() { + return qualifyingResultType; + } + + private static QualifyingInfo fromSelectionParameters(SelectionParameters selectionParameters) { + if ( selectionParameters == null ) { + return EMPTY; + } + return new QualifyingInfo( + selectionParameters.getQualifiers(), + selectionParameters.getQualifyingNames(), + selectionParameters.getResultType() + ); + } + } + + + public enum Type { + PREFER_UPDATE_MAPPING, + OBJECT_FACTORY, + LIFECYCLE_CALLBACK, + PRESENCE_CHECK, + SOURCE_PARAMETER_CHECK, + SELF_NOT_ALLOWED, + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java new file mode 100644 index 0000000000..91a30902e0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SourceRhsSelector.java @@ -0,0 +1,47 @@ +/* + * 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.model.source.selector; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.internal.model.common.ParameterBinding; +import org.mapstruct.ap.internal.model.source.Method; + +/** + * Selector that tries to resolve an ambiguity between methods that contain source parameters and + * {@link org.mapstruct.ap.internal.model.common.SourceRHS SourceRHS} type parameters. + * @author Filip Hrisafov + */ +public class SourceRhsSelector implements MethodSelector { + + @Override + public List> getMatchingMethods(List> candidates, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); + if ( candidates.size() < 2 || criteria.getSourceRHS() == null ) { + return candidates; + } + + List> sourceRHSFavoringCandidates = new ArrayList<>(); + + for ( SelectedMethod candidate : candidates ) { + for ( ParameterBinding parameterBinding : candidate.getParameterBindings() ) { + if ( parameterBinding.getSourceRHS() != null ) { + sourceRHSFavoringCandidates.add( candidate ); + break; + } + } + + } + + if ( !sourceRHSFavoringCandidates.isEmpty() ) { + return sourceRHSFavoringCandidates; + } + + return candidates; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java index 642da7b136..181c1f883b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java @@ -1,32 +1,16 @@ -/** - * Copyright 2012-2015 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.model.source.selector; import java.util.ArrayList; import java.util.List; - import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.util.TypeUtils; /** * This selector selects a best match based on the result type. @@ -38,23 +22,25 @@ */ public class TargetTypeSelector implements MethodSelector { - private final Types typeUtils; + private final TypeUtils typeUtils; - public TargetTypeSelector( Types typeUtils, Elements elementUtils ) { + public TargetTypeSelector( TypeUtils typeUtils ) { this.typeUtils = typeUtils; } @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType(); - if ( qualifyingTypeMirror != null ) { + if ( qualifyingTypeMirror != null && !criteria.isLifecycleCallbackRequired() ) { + + List> candidatesWithQualifyingTargetType = + new ArrayList<>( methods.size() ); - List candidatesWithQualifyingTargetType = new ArrayList(); - for ( T method : methods ) { - TypeMirror resultTypeMirror = method.getResultType().getTypeElement().asType(); + for ( SelectedMethod method : methods ) { + TypeMirror resultTypeMirror = method.getMethod().getResultType().getTypeElement().asType(); if ( typeUtils.isSameType( qualifyingTypeMirror, resultTypeMirror ) ) { candidatesWithQualifyingTargetType.add( method ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java index ff12732098..1a2389eba1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java @@ -1,30 +1,23 @@ -/** - * Copyright 2012-2015 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.model.source.selector; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.MethodMatcher; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; + +import static org.mapstruct.ap.internal.util.Collections.first; /** * Selects those methods from the given input set which match the given source and target types (via @@ -34,17 +27,206 @@ */ public class TypeSelector implements MethodSelector { + private FormattingMessager messager; + + public TypeSelector(FormattingMessager messager) { + this.messager = messager; + } + @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { - - List result = new ArrayList(); - for ( T method : methods ) { - if ( !method.isLifecycleCallbackMethod() && method.matches( Arrays.asList( sourceType ), targetType ) ) { - result.add( method ); + public List> getMatchingMethods(List> methods, + SelectionContext context) { + if ( methods.isEmpty() ) { + return methods; + } + + Type returnType = context.getReturnType(); + + List> result = new ArrayList<>(); + + List availableBindings = context.getAvailableParameterBindings(); + + for ( SelectedMethod method : methods ) { + List> parameterBindingPermutations = + getCandidateParameterBindingPermutations( availableBindings, method.getMethod().getParameters() ); + + if ( parameterBindingPermutations != null ) { + SelectedMethod matchingMethod = + getMatchingParameterBinding( returnType, context, method, parameterBindingPermutations ); + + if ( matchingMethod != null ) { + result.add( matchingMethod ); + } } } return result; } + + private SelectedMethod getMatchingParameterBinding(Type returnType, + SelectionContext context, SelectedMethod selectedMethodInfo, + List> parameterAssignmentVariants) { + + List> matchingParameterAssignmentVariants = new ArrayList<>( + parameterAssignmentVariants + ); + + Method selectedMethod = selectedMethodInfo.getMethod(); + + // remove all assignment variants that doesn't match the types from the method + matchingParameterAssignmentVariants.removeIf( parameterAssignments -> + !selectedMethod.matches( extractTypes( parameterAssignments ), returnType ) + ); + + if ( matchingParameterAssignmentVariants.isEmpty() ) { + // no matching variants found + return null; + } + else if ( matchingParameterAssignmentVariants.size() == 1 ) { + // we found exactly one set of variants, use this + selectedMethodInfo.setParameterBindings( first( matchingParameterAssignmentVariants ) ); + return selectedMethodInfo; + } + + // more than one variant matches, try to find one where also the parameter names are matching + // -> remove all variants where the binding var-name doesn't match the var-name of the parameter + List methodParameters = selectedMethod.getParameters(); + + matchingParameterAssignmentVariants.removeIf( parameterBindings -> + parameterBindingNotMatchesParameterVariableNames( + parameterBindings, + methodParameters + ) + ); + + + if ( matchingParameterAssignmentVariants.isEmpty() ) { + // we had some matching assignments before, but when checking the parameter names we can't find an + // appropriate one, in this case the user must chose identical parameter names for the mapping and lifecycle + // method + messager.printMessage( + selectedMethod.getExecutable(), + Message.LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS, + context.getMappingMethod() + ); + + return null; + } + + // there should never be more then one assignment left after checking the parameter names as it is not possible + // to use the same parameter name more then once + // -> we can use the first variant that is left (that should also be the only one) + selectedMethodInfo.setParameterBindings( first( matchingParameterAssignmentVariants ) ); + return selectedMethodInfo; + } + + /** + * Checks if the given parameter-bindings have the same variable name than the given parameters.
    + * The first entry in the parameter-bindings belongs to the first entry in the parameters and so on.
    + * + * @param parameterBindings List of parameter bindings + * @param parameters List of parameters, must have the same size than the {@code parameterBindings} list + * + * @return {@code true} as soon as there is a parameter with a different variable name than the binding + */ + private boolean parameterBindingNotMatchesParameterVariableNames(List parameterBindings, + List parameters) { + if ( parameterBindings.size() != parameters.size() ) { + return true; + } + + int i = 0; + for ( ParameterBinding parameterBinding : parameterBindings ) { + Parameter parameter = parameters.get( i++ ); + + // if the parameterBinding contains a parameter name we must ensure that this matches the name from the + // method parameter -> remove all variants where this is not the case + if ( parameterBinding.getVariableName() != null && + !parameter.getName().equals( parameterBinding.getVariableName() ) ) { + return true; + } + } + + return false; + } + + /** + * @param availableParams parameter bindings available in the scope of the method call + * @param methodParameters parameters of the method that is inspected + * @return all parameter binding permutations for which proper type checks need to be conducted. + */ + private static List> getCandidateParameterBindingPermutations( + List availableParams, + List methodParameters) { + + if ( methodParameters.size() > availableParams.size() ) { + return null; + } + + List> bindingPermutations = new ArrayList<>( 1 ); + bindingPermutations.add( new ArrayList<>( methodParameters.size() ) ); + + for ( Parameter methodParam : methodParameters ) { + List candidateBindings = + findCandidateBindingsForParameter( availableParams, methodParam ); + + if ( candidateBindings.isEmpty() ) { + return null; + } + + if ( candidateBindings.size() == 1 ) { + // short-cut to avoid list-copies for the usual case where only one binding fits + for ( List variant : bindingPermutations ) { + // add binding to each existing variant + variant.add( first( candidateBindings ) ); + } + } + else { + List> newVariants = + new ArrayList<>( bindingPermutations.size() * candidateBindings.size() ); + for ( List variant : bindingPermutations ) { + // create a copy of each variant for each binding + for ( ParameterBinding binding : candidateBindings ) { + List extendedVariant = + new ArrayList<>( methodParameters.size() ); + extendedVariant.addAll( variant ); + extendedVariant.add( binding ); + + newVariants.add( extendedVariant ); + } + } + + bindingPermutations = newVariants; + } + } + + return bindingPermutations; + } + + /** + * @param candidateParameters available for assignment. + * @param parameter that need assignment from one of the candidate parameter bindings. + * @return list of candidate parameter bindings that might be assignable. + */ + private static List findCandidateBindingsForParameter(List candidateParameters, + Parameter parameter) { + List result = new ArrayList<>( candidateParameters.size() ); + + for ( ParameterBinding candidate : candidateParameters ) { + if ( parameter.isTargetType() == candidate.isTargetType() + && parameter.isMappingTarget() == candidate.isMappingTarget() + && parameter.isMappingContext() == candidate.isMappingContext() + && parameter.isSourcePropertyName() == candidate.isSourcePropertyName() + && parameter.isTargetPropertyName() == candidate.isTargetPropertyName() ) { + result.add( candidate ); + } + } + + return result; + } + + private static List extractTypes(List parameters) { + return parameters.stream() + .map( ParameterBinding::getType ) + .collect( Collectors.toList() ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java index 04e6bbd0ec..a32cb250d5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java @@ -1,38 +1,28 @@ -/** - * Copyright 2012-2015 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.model.source.selector; import java.util.ArrayList; import java.util.List; - +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; -import javax.xml.bind.annotation.XmlElementDecl; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.prism.XmlElementDeclPrism; +import org.mapstruct.ap.internal.util.TypeUtils; /** - * Selects those methods with matching {@code name} and {@code scope} attributes of the {@link XmlElementDecl} - * annotation, if that is present. Matching happens in the following order: + * Finds the {@code XmlElementRef} annotation on a field (of the mapping result type or its + * super types) matching the + * target property name. Then selects those methods with matching {@code name} and {@code scope} attributes of the + * {@code XmlElementDecl} annotation, if that is present. Matching happens in the following + * order: *
      *
    1. Name and Scope matches
    2. *
    3. Scope matches
    4. @@ -42,49 +32,48 @@ * the given method is not annotated with {@code XmlElementDecl} it will be considered as matching. * * @author Sjaak Derksen + * + * @see JavaxXmlElementDeclSelector + * @see JakartaXmlElementDeclSelector */ -public class XmlElementDeclSelector implements MethodSelector { +abstract class XmlElementDeclSelector implements MethodSelector { - private final Types typeUtils; + private final TypeUtils typeUtils; - public XmlElementDeclSelector(Types typeUtils) { + XmlElementDeclSelector(TypeUtils typeUtils) { this.typeUtils = typeUtils; } @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { - - // only true source methods are qualifying - if ( !(mappingMethod instanceof SourceMethod) ) { - return methods; - } - - SourceMethod sourceMappingMethod = (SourceMethod) mappingMethod; - - List nameMatches = new ArrayList(); - List scopeMatches = new ArrayList(); - List nameAndScopeMatches = new ArrayList(); - - for ( T candidate : methods ) { - if ( !( candidate instanceof SourceMethod ) ) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + Type resultType = context.getMappingMethod().getResultType(); + String targetPropertyName = context.getSelectionCriteria().getTargetPropertyName(); + + List> nameMatches = new ArrayList<>(); + List> scopeMatches = new ArrayList<>(); + List> nameAndScopeMatches = new ArrayList<>(); + XmlElementRefInfo xmlElementRefInfo = + findXmlElementRef( resultType, targetPropertyName ); + + for ( SelectedMethod candidate : methods ) { + if ( !( candidate.getMethod() instanceof SourceMethod ) ) { continue; } - SourceMethod candidateMethod = (SourceMethod) candidate; - XmlElementDeclPrism xmlElememtDecl = XmlElementDeclPrism.getInstanceOn( candidateMethod.getExecutable() ); + SourceMethod candidateMethod = (SourceMethod) candidate.getMethod(); + XmlElementDeclInfo xmlElementDeclInfo = getXmlElementDeclInfo( candidateMethod.getExecutable() ); - if ( xmlElememtDecl == null ) { + if ( xmlElementDeclInfo == null ) { continue; } - String name = xmlElememtDecl.name(); - TypeMirror scope = xmlElememtDecl.scope(); - TypeMirror target = sourceMappingMethod.getExecutable().getReturnType(); + String name = xmlElementDeclInfo.nameValue(); + TypeMirror scope = xmlElementDeclInfo.scopeType(); - boolean nameIsSetAndMatches = name != null && name.equals( criteria.getTargetPropertyName() ); - boolean scopeIsSetAndMatches = scope != null && typeUtils.isSameType( scope, target ); + boolean nameIsSetAndMatches = name != null && name.equals( xmlElementRefInfo.nameValue() ); + boolean scopeIsSetAndMatches = + scope != null && typeUtils.isSameType( scope, xmlElementRefInfo.sourceType() ); if ( nameIsSetAndMatches ) { if ( scopeIsSetAndMatches ) { @@ -99,17 +88,114 @@ else if ( scopeIsSetAndMatches ) { } } - if ( nameAndScopeMatches.size() > 0 ) { + if ( !nameAndScopeMatches.isEmpty() ) { return nameAndScopeMatches; } - else if ( scopeMatches.size() > 0 ) { + else if ( !scopeMatches.isEmpty() ) { return scopeMatches; } - else if ( nameMatches.size() > 0 ) { + else if ( !nameMatches.isEmpty() ) { return nameMatches; } else { return methods; } } + + /** + * Iterate through resultType and its super types to find a field named targetPropertyName and return information + * about: + *
        + *
      • what the value of the name property of the XmlElementRef annotation on that field was
      • + *
      • on which type the field was found
      • + *
      + * + * @param resultType starting point of the iteration + * @param targetPropertyName name of the field we are looking for + * @return an XmlElementRefInfo containing the information + */ + private XmlElementRefInfo findXmlElementRef(Type resultType, String targetPropertyName) { + TypeMirror startingMirror = resultType.getTypeMirror(); + XmlElementRefInfo defaultInfo = new XmlElementRefInfo( targetPropertyName, startingMirror ); + if ( targetPropertyName == null ) { + /* + * sometimes MethodSelectors seem to be called with criteria.getTargetPropertyName() == null so we need to + * avoid NPEs for that case. + */ + return defaultInfo; + } + + TypeMirror currentMirror = startingMirror; + TypeElement currentElement = resultType.getTypeElement(); + + /* + * Outer loop for resultType and its super types. "currentElement" will be null once we reach Object and try to + * get a TypeElement for its super type. + */ + while ( currentElement != null ) { + /* + * Inner loop tries to find a field with the targetPropertyName and assumes that where the XmlElementRef is + * set + */ + for ( Element enclosed : currentElement.getEnclosedElements() ) { + if ( enclosed.getKind().equals( ElementKind.FIELD ) + && enclosed.getSimpleName().contentEquals( targetPropertyName ) ) { + XmlElementRefInfo xmlElementRefInfo = getXmlElementRefInfo( enclosed ); + if ( xmlElementRefInfo != null ) { + return new XmlElementRefInfo( xmlElementRefInfo.nameValue(), currentMirror ); + } + } + } + currentMirror = currentElement.getSuperclass(); + currentElement = (TypeElement) typeUtils.asElement( currentMirror ); + } + return defaultInfo; + } + + abstract XmlElementDeclInfo getXmlElementDeclInfo(Element element); + + abstract XmlElementRefInfo getXmlElementRefInfo(Element element); + + static class XmlElementRefInfo { + private final String nameValue; + private final TypeMirror sourceType; + + XmlElementRefInfo(String nameValue, TypeMirror sourceType) { + this.nameValue = nameValue; + this.sourceType = sourceType; + } + + String nameValue() { + return nameValue; + } + + TypeMirror sourceType() { + return sourceType; + } + } + + /** + * A class, whose purpose is to combine the use of + * {@link org.mapstruct.ap.internal.gem.XmlElementDeclGem} + * and + * {@link org.mapstruct.ap.internal.gem.jakarta.XmlElementDeclGem}. + */ + static class XmlElementDeclInfo { + + private final String nameValue; + private final TypeMirror scopeType; + + XmlElementDeclInfo(String nameValue, TypeMirror scopeType) { + this.nameValue = nameValue; + this.scopeType = scopeType; + } + + String nameValue() { + return nameValue; + } + + TypeMirror scopeType() { + return scopeType; + } + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/package-info.java index 6e57371389..60bfa41d1e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/naming/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/internal/naming/DefaultAccessorNamingStrategy.java deleted file mode 100644 index b93c064648..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/naming/DefaultAccessorNamingStrategy.java +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.naming; - -import java.beans.Introspector; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; - -import org.mapstruct.ap.spi.AccessorNamingStrategy; -import org.mapstruct.ap.spi.MethodType; - -/** - * The default JavaBeans-compliant implementation of the {@link AccessorNamingStrategy} service provider interface. - * - * @author Christian Schuster - */ -public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { - - @Override - public MethodType getMethodType(ExecutableElement method) { - if ( isGetterMethod( method ) ) { - return MethodType.GETTER; - } - else if ( isSetterMethod( method ) ) { - return MethodType.SETTER; - } - else if ( isAdderMethod( method ) ) { - return MethodType.ADDER; - } - else { - return MethodType.OTHER; - } - } - - private boolean isGetterMethod(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - - boolean isNonBooleanGetterName = methodName.startsWith( "get" ) && methodName.length() > 3 && - method.getReturnType().getKind() != TypeKind.VOID; - - boolean isBooleanGetterName = methodName.startsWith( "is" ) && methodName.length() > 2; - boolean returnTypeIsBoolean = method.getReturnType().getKind() == TypeKind.BOOLEAN || - "java.lang.Boolean".equals( getQualifiedName( method.getReturnType() ) ); - - return isNonBooleanGetterName || ( isBooleanGetterName && returnTypeIsBoolean ); - } - - public boolean isSetterMethod(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - - return methodName.startsWith( "set" ) && methodName.length() > 3; - } - - public boolean isAdderMethod(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - - return methodName.startsWith( "add" ) && methodName.length() > 3; - } - - - @Override - public String getPropertyName(ExecutableElement getterOrSetterMethod) { - String methodName = getterOrSetterMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) ); - } - - @Override - public String getElementName(ExecutableElement adderMethod) { - String methodName = adderMethod.getSimpleName().toString(); - return Introspector.decapitalize( methodName.substring( 3 ) ); - } - - @Override - public String getCollectionGetterName(String property) { - return "get" + property.substring( 0, 1 ).toUpperCase() + property.substring( 1 ); - } - - private static String getQualifiedName(TypeMirror type) { - DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { - @Override - public DeclaredType visitDeclared(DeclaredType t, Void p) { - return t; - } - }, - null - ); - - if ( declaredType == null ) { - return null; - } - - TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { - @Override - public TypeElement visitType(TypeElement e, Void p) { - return e; - } - }, - null - ); - - return typeElement != null ? typeElement.getQualifiedName().toString() : null; - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java new file mode 100644 index 0000000000..4607fc1ecf --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java @@ -0,0 +1,46 @@ +/* + * 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.option; + +/** + * The different compiler mapping options that are available in MapStruct. + * + * @author Filip Hrisafov + */ +public enum MappingOption { + + // CHECKSTYLE:OFF + SUPPRESS_GENERATOR_TIMESTAMP( "mapstruct.suppressGeneratorTimestamp" ), + SUPPRESS_GENERATOR_VERSION_INFO_COMMENT( "mapstruct.suppressGeneratorVersionInfoComment" ), + UNMAPPED_TARGET_POLICY("mapstruct.unmappedTargetPolicy"), + UNMAPPED_SOURCE_POLICY("mapstruct.unmappedSourcePolicy"), + DEFAULT_COMPONENT_MODEL("mapstruct.defaultComponentModel"), + DEFAULT_INJECTION_STRATEGY("mapstruct.defaultInjectionStrategy"), + ALWAYS_GENERATE_SERVICE_FILE("mapstruct.alwaysGenerateServicesFile"), + DISABLE_BUILDERS("mapstruct.disableBuilders"), + VERBOSE("mapstruct.verbose"), + NULL_VALUE_ITERABLE_MAPPING_STRATEGY("mapstruct.nullValueIterableMappingStrategy"), + NULL_VALUE_MAP_MAPPING_STRATEGY("mapstruct.nullValueMapMappingStrategy"), + DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR("mapstruct.disableLifecycleOverloadDeduplicateSelector"), + DISABLE_JSPECIFY("mapstruct.disableJSpecify"), + ; + // CHECKSTYLE:ON + + private final String optionName; + + MappingOption(String optionName) { + this.optionName = optionName; + } + + /** + * Returns the name of the option, which can be used in the compiler arguments. + * + * @return the name of the option + */ + public String getOptionName() { + return optionName; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java index 3871c999eb..1fd3585301 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/Options.java @@ -1,64 +1,98 @@ -/** - * Copyright 2012-2015 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.option; +import java.util.Locale; +import java.util.Map; + +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; /** * The options passed to the code generator. * * @author Andreas Gudian * @author Gunnar Morling + * @author Filip Hrisafov */ public class Options { - private final boolean suppressGeneratorTimestamp; - private final boolean suppressGeneratorVersionComment; - private final ReportingPolicy unmappedTargetPolicy; - private final boolean alwaysGenerateSpi; - private final String defaultComponentModel; - - public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, - ReportingPolicy unmappedTargetPolicy, - String defaultComponentModel, boolean alwaysGenerateSpi) { - this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; - this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; - this.unmappedTargetPolicy = unmappedTargetPolicy; - this.defaultComponentModel = defaultComponentModel; - this.alwaysGenerateSpi = alwaysGenerateSpi; + + private final Map options; + + public Options(Map options) { + this.options = options; } public boolean isSuppressGeneratorTimestamp() { - return suppressGeneratorTimestamp; + return parseBoolean( MappingOption.SUPPRESS_GENERATOR_TIMESTAMP ); } public boolean isSuppressGeneratorVersionComment() { - return suppressGeneratorVersionComment; + return parseBoolean( MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ); } - public ReportingPolicy getUnmappedTargetPolicy() { - return unmappedTargetPolicy; + public ReportingPolicyGem getUnmappedTargetPolicy() { + return parseEnum( MappingOption.UNMAPPED_TARGET_POLICY, ReportingPolicyGem.class ); + } + + public ReportingPolicyGem getUnmappedSourcePolicy() { + return parseEnum( MappingOption.UNMAPPED_SOURCE_POLICY, ReportingPolicyGem.class ); } public String getDefaultComponentModel() { - return defaultComponentModel; + return options.get( MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName() ); + } + + public String getDefaultInjectionStrategy() { + return options.get( MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName() ); } public boolean isAlwaysGenerateSpi() { - return alwaysGenerateSpi; + return parseBoolean( MappingOption.ALWAYS_GENERATE_SERVICE_FILE ); + } + + public boolean isDisableBuilders() { + return parseBoolean( MappingOption.DISABLE_BUILDERS ); + } + + public boolean isVerbose() { + return parseBoolean( MappingOption.VERBOSE ); + } + + public NullValueMappingStrategyGem getNullValueIterableMappingStrategy() { + return parseEnum( MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, NullValueMappingStrategyGem.class ); + } + + public NullValueMappingStrategyGem getNullValueMapMappingStrategy() { + return parseEnum( MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY, NullValueMappingStrategyGem.class ); + } + + public boolean isDisableLifecycleOverloadDeduplicateSelector() { + return parseBoolean( MappingOption.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR ); + } + + public boolean isDisableJSpecify() { + return parseBoolean( MappingOption.DISABLE_JSPECIFY ); + } + + private boolean parseBoolean(MappingOption option) { + if ( options.isEmpty() ) { + return false; + } + return Boolean.parseBoolean( options.get( option.getOptionName() ) ); + } + + private > E parseEnum(MappingOption option, Class enumType) { + if ( options.isEmpty() ) { + return null; + } + String value = options.get( option.getOptionName() ); + if ( value == null ) { + return null; + } + return Enum.valueOf( enumType, value.toUpperCase( Locale.ROOT ) ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/OptionsHelper.java b/processor/src/main/java/org/mapstruct/ap/internal/option/OptionsHelper.java deleted file mode 100644 index 3b87c86524..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/OptionsHelper.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.option; - - -/** - * Helper class for dealing with {@link Options}. - * - * @author Andreas Gudian - */ -public class OptionsHelper { - - private OptionsHelper() { - } - - /** - * @param options the options - * @param locallyDeclaredComponentModel the locally declared component model - * - * @return the effective component model to be used - */ - public static String getEffectiveComponentModel(Options options, String locallyDeclaredComponentModel) { - if ( "default".equals( locallyDeclaredComponentModel ) ) { - return options.getDefaultComponentModel(); - } - - return locallyDeclaredComponentModel; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/ReportingPolicy.java b/processor/src/main/java/org/mapstruct/ap/internal/option/ReportingPolicy.java deleted file mode 100644 index 4f04786016..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/ReportingPolicy.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.option; - -import javax.tools.Diagnostic; -import javax.tools.Diagnostic.Kind; - -/** - * Possible issue reporting policies. Duplicates the enum of the same name from - * the core module as this can't be referenced here. - * - * @author Gunnar Morling - */ -public enum ReportingPolicy { - - IGNORE( null, false, false ), - WARN( Kind.WARNING, true, false ), - ERROR( Kind.ERROR, true, true ); - - private final Diagnostic.Kind diagnosticKind; - private final boolean requiresReport; - private final boolean failsBuild; - - private ReportingPolicy(Diagnostic.Kind diagnosticKind, boolean requiresReport, boolean failsBuild) { - this.requiresReport = requiresReport; - this.diagnosticKind = diagnosticKind; - this.failsBuild = failsBuild; - } - - public Diagnostic.Kind getDiagnosticKind() { - return diagnosticKind; - } - - public boolean requiresReport() { - return requiresReport; - } - - public boolean failsBuild() { - return failsBuild; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/option/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/option/package-info.java index 3929f457dd..87bb426eaf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/option/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/CollectionMappingStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/CollectionMappingStrategyPrism.java deleted file mode 100644 index 09af1faa6c..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/CollectionMappingStrategyPrism.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.prism; - -/** - * Prism for the enum {@link org.mapstruct.CollectionMappingStrategy} - * - * @author Andreas Gudian - */ -public enum CollectionMappingStrategyPrism { - - ACCESSOR_ONLY, - SETTER_PREFERRED, - ADDER_PREFERRED; -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingInheritanceStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingInheritanceStrategyPrism.java deleted file mode 100644 index 599de90981..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingInheritanceStrategyPrism.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.prism; - - -/** - * Prism for the enum {@link org.mapstruct.MappingInheritanceStrategy} - * - * @author Andreas Gudian - */ -public enum MappingInheritanceStrategyPrism { - EXPLICIT, - AUTO_INHERIT_FROM_CONFIG; -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValueMappingStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValueMappingStrategyPrism.java deleted file mode 100644 index 591908cc4a..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValueMappingStrategyPrism.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.prism; - -/** - * Prism for the enum {@link org.mapstruct.NullValueMappingStrategy} - * - * @author Sjaak Derksen - */ -public enum NullValueMappingStrategyPrism { - - RETURN_NULL, - RETURN_DEFAULT; -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java deleted file mode 100644 index 2c5937aae7..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.prism; - -import javax.xml.bind.annotation.XmlElementDecl; - -import net.java.dev.hickory.prism.GeneratePrism; -import net.java.dev.hickory.prism.GeneratePrisms; - -import org.mapstruct.AfterMapping; -import org.mapstruct.BeanMapping; -import org.mapstruct.BeforeMapping; -import org.mapstruct.DecoratedWith; -import org.mapstruct.InheritConfiguration; -import org.mapstruct.InheritInverseConfiguration; -import org.mapstruct.IterableMapping; -import org.mapstruct.MapMapping; -import org.mapstruct.Mapper; -import org.mapstruct.MapperConfig; -import org.mapstruct.Mapping; -import org.mapstruct.MappingTarget; -import org.mapstruct.Mappings; -import org.mapstruct.Qualifier; -import org.mapstruct.TargetType; - -/** - * Triggers the generation of prism types using Hickory. - * - * @author Gunnar Morling - */ -@GeneratePrisms({ - @GeneratePrism(value = Mapper.class, publicAccess = true), - @GeneratePrism(value = Mapping.class, publicAccess = true), - @GeneratePrism(value = Mappings.class, publicAccess = true), - @GeneratePrism(value = IterableMapping.class, publicAccess = true), - @GeneratePrism(value = BeanMapping.class, publicAccess = true), - @GeneratePrism(value = MapMapping.class, publicAccess = true), - @GeneratePrism(value = TargetType.class, publicAccess = true), - @GeneratePrism(value = MappingTarget.class, publicAccess = true), - @GeneratePrism(value = DecoratedWith.class, publicAccess = true), - @GeneratePrism(value = MapperConfig.class, publicAccess = true), - @GeneratePrism(value = InheritConfiguration.class, publicAccess = true), - @GeneratePrism(value = InheritInverseConfiguration.class, publicAccess = true), - @GeneratePrism(value = Qualifier.class, publicAccess = true), - @GeneratePrism(value = AfterMapping.class, publicAccess = true), - @GeneratePrism(value = BeforeMapping.class, publicAccess = true), - - // external types - @GeneratePrism(value = XmlElementDecl.class, publicAccess = true) -}) -public class PrismGenerator { - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/package-info.java deleted file mode 100644 index ed3cda1c99..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/package-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright 2012-2015 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. - */ -/** - *

      - * This package contains the generated prism types for accessing the MapStruct annotations in a comfortable way. - *

      - */ -package org.mapstruct.ap.internal.prism; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java index 473528849f..bbd695412a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/AnnotationBasedComponentModelProcessor.java @@ -1,47 +1,39 @@ -/** - * Copyright 2012-2015 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.processor; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.ListIterator; - +import java.util.Set; +import java.util.stream.Collectors; import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.gem.InjectionStrategyGem; +import org.mapstruct.ap.internal.model.AnnotatedConstructor; +import org.mapstruct.ap.internal.model.AnnotatedSetter; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.AnnotationMapperReference; import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Field; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.MapperReference; +import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.option.OptionsHelper; -import org.mapstruct.ap.internal.util.MapperConfiguration; +import org.mapstruct.ap.internal.model.source.MapperOptions; /** - * An {@link ModelElementProcessor} which converts the given {@link Mapper} - * object into an annotation based component model in case a matching model is selected as - * target component model for this mapper. + * An {@link ModelElementProcessor} which converts the given {@link Mapper} object into an annotation based component + * model in case a matching model is selected as target component model for this mapper. * * @author Gunnar Morling * @author Andreas Gudian + * @author Kevin Grüneberg */ public abstract class AnnotationBasedComponentModelProcessor implements ModelElementProcessor { @@ -51,13 +43,12 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper) { this.typeFactory = context.getTypeFactory(); - String componentModel = MapperConfiguration.getInstanceOn( mapperTypeElement ).componentModel(); - String effectiveComponentModel = OptionsHelper.getEffectiveComponentModel( - context.getOptions(), - componentModel - ); + MapperOptions mapperAnnotation = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() ); + + String componentModel = mapperAnnotation.componentModel(); + InjectionStrategyGem injectionStrategy = mapperAnnotation.getInjectionStrategy(); - if ( !getComponentModelIdentifier().equalsIgnoreCase( effectiveComponentModel ) ) { + if ( !getComponentModelIdentifier().equalsIgnoreCase( componentModel ) ) { return mapper; } @@ -69,56 +60,238 @@ public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, M mapper.removeDecorator(); } else if ( mapper.getDecorator() != null ) { - adjustDecorator( mapper ); + adjustDecorator( mapper, injectionStrategy ); } List annotations = getMapperReferenceAnnotations(); - ListIterator iterator = mapper.getReferencedMappers().listIterator(); + ListIterator iterator = mapper.getFields().listIterator(); + while ( iterator.hasNext() ) { - MapperReference reference = iterator.next(); - iterator.remove(); - iterator.add( replacementMapperReference( reference, annotations ) ); + + Field reference = iterator.next(); + if ( reference instanceof MapperReference ) { + iterator.remove(); + iterator.add( replacementMapperReference( reference, annotations, injectionStrategy ) ); + } + } + + if ( injectionStrategy == InjectionStrategyGem.CONSTRUCTOR ) { + buildConstructors( mapper ); + } + else if ( injectionStrategy == InjectionStrategyGem.SETTER ) { + buildSetters( mapper ); } return mapper; } - protected void adjustDecorator(Mapper mapper) { + protected void adjustDecorator(Mapper mapper, InjectionStrategyGem injectionStrategy) { Decorator decorator = mapper.getDecorator(); - for ( Annotation typeAnnotation : getDecoratorAnnotations() ) { + for ( Annotation typeAnnotation : getDecoratorAnnotations( decorator ) ) { decorator.addAnnotation( typeAnnotation ); } decorator.removeConstructor(); - List annotations = getDelegatorReferenceAnnotations( mapper ); - List replacement = new ArrayList(); + List replacement = new ArrayList<>(); if ( !decorator.getMethods().isEmpty() ) { for ( Field field : decorator.getFields() ) { - replacement.add( replacementMapperReference( field, annotations ) ); + replacement.add( replacementMapperReference( field, annotations, injectionStrategy ) ); } } decorator.setFields( replacement ); } + private List toMapperReferences(List fields) { + List mapperReferences = new ArrayList<>(); + for ( Field field : fields ) { + if ( field instanceof MapperReference ) { + mapperReferences.add( (MapperReference) field ); + } + } + return mapperReferences; + } + + private void buildSetters(Mapper mapper) { + List mapperReferences = toMapperReferences( mapper.getFields() ); + for ( MapperReference mapperReference : mapperReferences ) { + if ( mapperReference.isUsed() ) { + AnnotatedSetter setter = new AnnotatedSetter( + mapperReference, + getMapperReferenceAnnotations(), + Collections.emptyList() + ); + mapper.getMethods().add( setter ); + } + } + + Decorator decorator = mapper.getDecorator(); + if ( decorator != null ) { + List mapperReferenceAnnotations = getMapperReferenceAnnotations(); + Set mapperReferenceAnnotationsTypes = mapperReferenceAnnotations + .stream() + .map( Annotation::getType ) + .collect( Collectors.toSet() ); + for ( Field field : decorator.getFields() ) { + if ( field instanceof AnnotationMapperReference ) { + + List fieldAnnotations = ( (AnnotationMapperReference) field ).getAnnotations(); + + List qualifiers = extractMissingAnnotations( + fieldAnnotations, + mapperReferenceAnnotationsTypes + ); + + decorator.getMethods().add( new AnnotatedSetter( field, mapperReferenceAnnotations, qualifiers ) ); + } + } + } + } + + private void buildConstructors(Mapper mapper) { + if ( !toMapperReferences( mapper.getFields() ).isEmpty() ) { + AnnotatedConstructor annotatedConstructor = buildAnnotatedConstructorForMapper( mapper ); + + if ( !annotatedConstructor.getMapperReferences().isEmpty() ) { + mapper.setConstructor( annotatedConstructor ); + } + } + + Decorator decorator = mapper.getDecorator(); + + if ( decorator != null ) { + AnnotatedConstructor decoratorConstructor = buildAnnotatedConstructorForDecorator( decorator ); + if ( !decoratorConstructor.getMapperReferences().isEmpty() ) { + decorator.setConstructor( decoratorConstructor ); + } + } + } + + private AnnotatedConstructor buildAnnotatedConstructorForMapper(Mapper mapper) { + List mapperReferences = toMapperReferences( mapper.getFields() ); + List mapperReferencesForConstructor = + new ArrayList<>( mapperReferences.size() ); + + for ( MapperReference mapperReference : mapperReferences ) { + if ( mapperReference.isUsed() ) { + mapperReferencesForConstructor.add( (AnnotationMapperReference) mapperReference ); + } + } + + List mapperReferenceAnnotations = getMapperReferenceAnnotations(); + + removeDuplicateAnnotations( mapperReferencesForConstructor, mapperReferenceAnnotations ); + + return AnnotatedConstructor.forComponentModels( + mapper.getName(), + mapperReferencesForConstructor, + mapperReferenceAnnotations, + mapper.getConstructor(), + additionalPublicEmptyConstructor() + ); + } + + private AnnotatedConstructor buildAnnotatedConstructorForDecorator(Decorator decorator) { + List mapperReferencesForConstructor = + new ArrayList<>( decorator.getFields().size() ); + + for ( Field field : decorator.getFields() ) { + if ( field instanceof AnnotationMapperReference ) { + mapperReferencesForConstructor.add( (AnnotationMapperReference) field ); + } + } + + List mapperReferenceAnnotations = getMapperReferenceAnnotations(); + + removeDuplicateAnnotations( mapperReferencesForConstructor, mapperReferenceAnnotations ); + + return AnnotatedConstructor.forComponentModels( + decorator.getName(), + mapperReferencesForConstructor, + mapperReferenceAnnotations, + decorator.getConstructor(), + additionalPublicEmptyConstructor() + ); + } + + /** + * Removes duplicate constructor parameter annotations. If an annotation is already present on the constructor, it + * does not have be defined on the constructor parameter, too. For example, for CDI, the javax.inject.Inject + * annotation is on the constructor and does not need to be on the constructor parameters. + * + * @param annotationMapperReferences annotations to annotate the constructor parameter with + * @param mapperReferenceAnnotations annotations to annotate the constructor with + */ + private void removeDuplicateAnnotations(List annotationMapperReferences, + List mapperReferenceAnnotations) { + ListIterator mapperReferenceIterator = annotationMapperReferences.listIterator(); + + Set mapperReferenceAnnotationsTypes = new HashSet<>(); + for ( Annotation annotation : mapperReferenceAnnotations ) { + mapperReferenceAnnotationsTypes.add( annotation.getType() ); + } + + while ( mapperReferenceIterator.hasNext() ) { + AnnotationMapperReference annotationMapperReference = mapperReferenceIterator.next(); + mapperReferenceIterator.remove(); + + List qualifiers = extractMissingAnnotations( + annotationMapperReference.getAnnotations(), + mapperReferenceAnnotationsTypes + ); + + mapperReferenceIterator.add( annotationMapperReference.withNewAnnotations( qualifiers ) ); + } + } + + /** + * Extract all annotations from {@code annotations} that do not have a type in {@code annotationTypes}. + * + * @param annotations the annotations from which we need to extract information + * @param annotationTypes the annotation types to ignore + * @return the annotations that are not in the {@code annotationTypes} + */ + private List extractMissingAnnotations(List annotations, + Set annotationTypes) { + List qualifiers = new ArrayList<>(); + for ( Annotation annotation : annotations ) { + if ( !annotationTypes.contains( annotation.getType() ) ) { + qualifiers.add( annotation ); + } + } + return qualifiers; + } + + protected boolean additionalPublicEmptyConstructor() { + return false; + } + protected List getDelegatorReferenceAnnotations(Mapper mapper) { return Collections.emptyList(); } /** * @param originalReference the reference to be replaced - * + * @param annotations the list of annotations + * @param injectionStrategy strategy for injection * @return the mapper reference replacing the original one */ - protected MapperReference replacementMapperReference(Field originalReference, List annotations) { + protected Field replacementMapperReference(Field originalReference, List annotations, + InjectionStrategyGem injectionStrategy) { + boolean finalField = + injectionStrategy == InjectionStrategyGem.CONSTRUCTOR && !additionalPublicEmptyConstructor(); + + boolean includeAnnotationsOnField = injectionStrategy == InjectionStrategyGem.FIELD; + return new AnnotationMapperReference( originalReference.getType(), originalReference.getVariableName(), annotations, - originalReference.isUsed() - ); + originalReference.isUsed(), + finalField, + includeAnnotationsOnField ); } /** @@ -127,6 +300,7 @@ protected MapperReference replacementMapperReference(Field originalReference, Li protected abstract String getComponentModelIdentifier(); /** + * @param mapper the mapper * @return the annotation(s) to be added at the mapper type implementation */ protected abstract List getTypeAnnotations(Mapper mapper); @@ -134,7 +308,7 @@ protected MapperReference replacementMapperReference(Field originalReference, Li /** * @return the annotation(s) to be added at the decorator of the mapper */ - protected List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Collections.emptyList(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java index a9f4ebf1dc..d8f136034b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/CdiComponentProcessor.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.processor; @@ -22,8 +9,11 @@ import java.util.Collections; import java.util.List; +import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.AnnotationProcessingException; /** * A {@link ModelElementProcessor} which converts the given {@link Mapper} @@ -36,23 +26,48 @@ public class CdiComponentProcessor extends AnnotationBasedComponentModelProcesso @Override protected String getComponentModelIdentifier() { - return "cdi"; + return MappingConstantsGem.ComponentModelGem.CDI; } @Override protected List getTypeAnnotations(Mapper mapper) { return Collections.singletonList( - new Annotation( getTypeFactory().getType( "javax.enterprise.context.ApplicationScoped" ) ) + new Annotation( getType( "ApplicationScoped" ) ) ); } @Override protected List getMapperReferenceAnnotations() { - return Arrays.asList( new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) ) ); + return Arrays.asList( new Annotation( getType( "Inject" ) ) ); } @Override protected boolean requiresGenerationOfDecoratorClass() { return false; } + + @Override + protected boolean additionalPublicEmptyConstructor() { + return true; + } + + private Type getType(String simpleName) { + String javaxPrefix = "javax.inject."; + String jakartaPrefix = "jakarta.inject."; + if ( "ApplicationScoped".equals( simpleName ) ) { + javaxPrefix = "javax.enterprise.context."; + jakartaPrefix = "jakarta.enterprise.context."; + } + if ( getTypeFactory().isTypeAvailable( javaxPrefix + simpleName ) ) { + return getTypeFactory().getType( javaxPrefix + simpleName ); + } + + if ( getTypeFactory().isTypeAvailable( jakartaPrefix + simpleName ) ) { + return getTypeFactory().getType( jakartaPrefix + simpleName ); + } + + throw new AnnotationProcessingException( + "Couldn't find any of the CDI or Jakarta CDI Dependency types." + + " Are you missing a dependency on your classpath?" ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java index 26ff46e389..7a0596d4c2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultModelElementProcessorContext.java @@ -1,39 +1,34 @@ -/** - * Copyright 2012-2015 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.processor; +import java.util.Map; +import java.util.stream.IntStream; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; +import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext; +import org.mapstruct.ap.internal.util.AccessorNamingUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.NullabilityResolver; +import org.mapstruct.ap.internal.util.RoundContext; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.version.VersionInformation; +import org.mapstruct.ap.spi.EnumMappingStrategy; +import org.mapstruct.ap.spi.EnumTransformationStrategy; /** * Default implementation of the processor context. @@ -47,16 +42,33 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { private final Options options; private final TypeFactory typeFactory; private final VersionInformation versionInformation; + private final TypeUtils delegatingTypes; + private final ElementUtils delegatingElements; + private final AccessorNamingUtils accessorNaming; + private final NullabilityResolver nullabilityResolver; + private final RoundContext roundContext; + + public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options, + RoundContext roundContext, Map notToBeImported, TypeElement mapperElement) { - public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options) { this.processingEnvironment = processingEnvironment; - this.messager = new DelegatingMessager( processingEnvironment.getMessager() ); + this.messager = new DelegatingMessager( processingEnvironment.getMessager(), options.isVerbose() ); + this.accessorNaming = roundContext.getAnnotationProcessorContext().getAccessorNaming(); + this.nullabilityResolver = roundContext.getAnnotationProcessorContext().getNullabilityResolver(); + this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment ); + this.delegatingTypes = TypeUtils.create( processingEnvironment, versionInformation ); + this.delegatingElements = ElementUtils.create( processingEnvironment, versionInformation, mapperElement ); + this.roundContext = roundContext; this.typeFactory = new TypeFactory( - processingEnvironment.getElementUtils(), - processingEnvironment.getTypeUtils() + delegatingElements, + delegatingTypes, + messager, + roundContext, + notToBeImported, + options.isVerbose(), + versionInformation ); this.options = options; - this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment ); } @Override @@ -65,13 +77,13 @@ public Filer getFiler() { } @Override - public Types getTypeUtils() { - return processingEnvironment.getTypeUtils(); + public TypeUtils getTypeUtils() { + return delegatingTypes; } @Override - public Elements getElementUtils() { - return processingEnvironment.getElementUtils(); + public ElementUtils getElementUtils() { + return delegatingElements; } @Override @@ -84,6 +96,26 @@ public FormattingMessager getMessager() { return messager; } + @Override + public AccessorNamingUtils getAccessorNaming() { + return accessorNaming; + } + + @Override + public NullabilityResolver getNullabilityResolver() { + return nullabilityResolver; + } + + @Override + public Map getEnumTransformationStrategies() { + return roundContext.getAnnotationProcessorContext().getEnumTransformationStrategies(); + } + + @Override + public EnumMappingStrategy getEnumMappingStrategy() { + return roundContext.getAnnotationProcessorContext().getEnumMappingStrategy(); + } + @Override public Options getOptions() { return options; @@ -99,19 +131,21 @@ public boolean isErroneous() { return messager.isErroneous(); } - private static class DelegatingMessager implements FormattingMessager { + private static final class DelegatingMessager implements FormattingMessager { private final Messager delegate; private boolean isErroneous = false; + private final boolean verbose; - public DelegatingMessager(Messager delegate) { + DelegatingMessager(Messager delegate, boolean verbose) { this.delegate = delegate; + this.verbose = verbose; } @Override public void printMessage(Message msg, Object... args) { String message = String.format( msg.getDescription(), args ); - delegate.printMessage( msg.getDiagnosticKind(), message); + delegate.printMessage( msg.getDiagnosticKind(), message ); if ( msg.getDiagnosticKind() == Kind.ERROR ) { isErroneous = true; } @@ -128,10 +162,15 @@ public void printMessage(Element e, Message msg, Object... args) { @Override public void printMessage(Element e, AnnotationMirror a, Message msg, Object... args) { - String message = String.format( msg.getDescription(), args ); - delegate.printMessage( msg.getDiagnosticKind(), message, e, a ); - if ( msg.getDiagnosticKind() == Kind.ERROR ) { - isErroneous = true; + if ( a == null ) { + printMessage( e, msg, args ); + } + else { + String message = String.format( msg.getDescription(), args ); + delegate.printMessage( msg.getDiagnosticKind(), message, e, a ); + if ( msg.getDiagnosticKind() == Kind.ERROR ) { + isErroneous = true; + } } } @@ -145,6 +184,16 @@ public void printMessage(Element e, AnnotationMirror a, AnnotationValue v, Messa } } + public void note( int level, Message msg, Object... args ) { + if ( verbose ) { + StringBuilder builder = new StringBuilder(); + IntStream.range( 0, level ).mapToObj( i -> "-" ).forEach( builder::append ); + builder.append( " MapStruct: " ).append( String.format( msg.getDescription(), args ) ); + delegate.printMessage( Kind.NOTE, builder.toString() ); + } + } + + @Override public boolean isErroneous() { return isErroneous; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java index bbbb7f2d08..f8053c92ab 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/DefaultVersionInformation.java @@ -1,29 +1,18 @@ -/** - * Copyright 2012-2015 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.processor; import java.io.IOException; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URL; import java.util.jar.Manifest; - import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; import org.mapstruct.ap.internal.version.VersionInformation; @@ -37,21 +26,38 @@ */ public class DefaultVersionInformation implements VersionInformation { private static final String JAVAC_PE_CLASS = "com.sun.tools.javac.processing.JavacProcessingEnvironment"; + private static final String COMPILER_NAME_JAVAC = "javac"; + private static final String JDT_IDE_PE_CLASS = "org.eclipse.jdt.internal.apt.pluggable.core.dispatch.IdeBuildProcessingEnvImpl"; private static final String JDT_BATCH_PE_CLASS = "org.eclipse.jdt.internal.compiler.apt.dispatch.BatchProcessingEnvImpl"; + private static final String COMPILER_NAME_ECLIPSE_JDT = "Eclipse JDT"; private static final String MAP_STRUCT_VERSION = initMapStructVersion(); private final String runtimeVersion; private final String runtimeVendor; private final String compiler; - - DefaultVersionInformation(String runtimeVersion, String runtimeVendor, String compiler) { + private final boolean sourceVersionAtLeast9; + private final boolean sourceVersionAtLeast11; + private final boolean sourceVersionAtLeast14; + private final boolean sourceVersionAtLeast19; + private final boolean eclipseJDT; + private final boolean javac; + + DefaultVersionInformation(String runtimeVersion, String runtimeVendor, String compiler, + SourceVersion sourceVersion) { this.runtimeVersion = runtimeVersion; this.runtimeVendor = runtimeVendor; this.compiler = compiler; + this.eclipseJDT = compiler.startsWith( COMPILER_NAME_ECLIPSE_JDT ); + this.javac = compiler.startsWith( COMPILER_NAME_JAVAC ); + // If the difference between the source version and RELEASE_6 is more that 2 than we are at least on 9 + this.sourceVersionAtLeast9 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 2; + this.sourceVersionAtLeast11 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 4; + this.sourceVersionAtLeast14 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 7; + this.sourceVersionAtLeast19 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 12; } @Override @@ -74,29 +80,86 @@ public String getCompiler() { return this.compiler; } + @Override + public boolean isSourceVersionAtLeast9() { + return sourceVersionAtLeast9; + } + + @Override + public boolean isSourceVersionAtLeast11() { + return sourceVersionAtLeast11; + } + + @Override + public boolean isSourceVersionAtLeast14() { + return sourceVersionAtLeast14; + } + + @Override + public boolean isSourceVersionAtLeast19() { + return sourceVersionAtLeast19; + } + + @Override + public boolean isEclipseJDTCompiler() { + return eclipseJDT; + } + + @Override + public boolean isJavacCompiler() { + return javac; + } + static DefaultVersionInformation fromProcessingEnvironment(ProcessingEnvironment processingEnv) { String runtimeVersion = System.getProperty( "java.version" ); String runtimeVendor = System.getProperty( "java.vendor" ); String compiler = getCompiler( processingEnv ); - return new DefaultVersionInformation( runtimeVersion, runtimeVendor, compiler ); + return new DefaultVersionInformation( + runtimeVersion, + runtimeVendor, + compiler, + processingEnv.getSourceVersion() + ); } private static String getCompiler(ProcessingEnvironment processingEnv) { - String className = processingEnv.getClass().getName(); + String className; + if ( Proxy.isProxyClass( processingEnv.getClass() ) ) { + // IntelliJ IDEA wraps the ProcessingEnvironment in a Proxy. + // Therefore we need smarter logic to determine the type of the compiler + String processingEnvToString = processingEnv.toString(); + if ( processingEnvToString.contains( COMPILER_NAME_JAVAC ) ) { + // The toString of the javac ProcessingEnvironment is "javac ProcessingEnvironment" + className = JAVAC_PE_CLASS; + } + else if ( processingEnvToString.startsWith( JDT_BATCH_PE_CLASS ) ) { + // The toString of the JDT Batch is from Object#toString so it contains the class name + className = JDT_BATCH_PE_CLASS; + } + else { + InvocationHandler invocationHandler = Proxy.getInvocationHandler( processingEnv ); + return "Proxy handler " + invocationHandler.getClass() + " from " + + getLibraryName( invocationHandler.getClass(), false ); + } + } + else { + className = processingEnv.getClass().getName(); + } if ( className.equals( JAVAC_PE_CLASS ) ) { - return "javac"; + return COMPILER_NAME_JAVAC; } if ( className.equals( JDT_IDE_PE_CLASS ) ) { // the processing environment for the IDE integrated APT is in a different bundle than the APT classes - return "Eclipse JDT (IDE) " + getLibraryName( processingEnv.getTypeUtils().getClass(), true ); + return COMPILER_NAME_ECLIPSE_JDT + " (IDE) " + + getLibraryName( processingEnv.getTypeUtils().getClass(), true ); } if ( className.equals( JDT_BATCH_PE_CLASS ) ) { - return "Eclipse JDT (Batch) " + getLibraryName( processingEnv.getClass(), true ); + return COMPILER_NAME_ECLIPSE_JDT + " (Batch) " + getLibraryName( processingEnv.getClass(), true ); } return processingEnv.getClass().getSimpleName() + " from " + getLibraryName( processingEnv.getClass(), false ); @@ -115,7 +178,10 @@ private static String getLibraryName(Class clazz, boolean preferVersionOnly) } } - if ( "jar".equals( resource.getProtocol() ) ) { + if ( resource == null ) { + return ""; + } + else if ( "jar".equals( resource.getProtocol() ) ) { return extractJarFileName( resource.getFile() ); } else if ( "jrt".equals( resource.getProtocol() ) ) { @@ -129,6 +195,9 @@ else if ( "bundleresource".equals( resource.getProtocol() ) && manifest != null } private static Manifest openManifest(String classFileName, URL resource) { + if ( resource == null ) { + return null; + } try { URL manifestUrl = createManifestUrl( classFileName, resource ); return new Manifest( manifestUrl.openStream() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaCdiComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaCdiComponentProcessor.java new file mode 100644 index 0000000000..11c765668e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaCdiComponentProcessor.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.ap.internal.processor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.mapstruct.ap.internal.gem.MappingConstantsGem; +import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Mapper; + +/** + * A {@link ModelElementProcessor} which converts the given {@link Mapper} + * object into an application-scoped Jakarta CDI bean in case Jakarta CDI + * is configured as the target component model for this mapper. + * + * @author Filip Hrisafov + */ +public class JakartaCdiComponentProcessor extends AnnotationBasedComponentModelProcessor { + + @Override + protected String getComponentModelIdentifier() { + return MappingConstantsGem.ComponentModelGem.JAKARTA_CDI; + } + + @Override + protected List getTypeAnnotations(Mapper mapper) { + return Collections.singletonList( + new Annotation( getTypeFactory().getType( "jakarta.enterprise.context.ApplicationScoped" ) ) + ); + } + + @Override + protected List getMapperReferenceAnnotations() { + return Arrays.asList( new Annotation( getTypeFactory().getType( "jakarta.inject.Inject" ) ) ); + } + + @Override + protected boolean requiresGenerationOfDecoratorClass() { + return false; + } + + @Override + protected boolean additionalPublicEmptyConstructor() { + return true; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java new file mode 100644 index 0000000000..86c95d612b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/JakartaComponentProcessor.java @@ -0,0 +1,84 @@ +/* + * 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.processor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.mapstruct.ap.internal.gem.MappingConstantsGem; +import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; +import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; + +/** + * A {@link ModelElementProcessor} which converts the given {@link Mapper} + * object into a Jakarta Inject style bean in case "jakarta" is configured as the + * target component model for this mapper. + * + * @author Filip Hrisafov + */ +public class JakartaComponentProcessor extends AnnotationBasedComponentModelProcessor { + @Override + protected String getComponentModelIdentifier() { + return MappingConstantsGem.ComponentModelGem.JAKARTA; + } + + @Override + protected List getTypeAnnotations(Mapper mapper) { + if ( mapper.getDecorator() == null ) { + return Arrays.asList( singleton(), named() ); + } + else { + return Arrays.asList( singleton(), namedDelegate( mapper ) ); + } + } + + @Override + protected List getDecoratorAnnotations(Decorator decorator) { + return Arrays.asList( singleton(), named() ); + } + + @Override + protected List getDelegatorReferenceAnnotations(Mapper mapper) { + return Arrays.asList( inject(), namedDelegate( mapper ) ); + } + + @Override + protected List getMapperReferenceAnnotations() { + return Collections.singletonList( inject() ); + } + + @Override + protected boolean requiresGenerationOfDecoratorClass() { + return true; + } + + private Annotation singleton() { + return new Annotation( getTypeFactory().getType( "jakarta.inject.Singleton" ) ); + } + + private Annotation named() { + return new Annotation( getTypeFactory().getType( "jakarta.inject.Named" ) ); + } + + private Annotation namedDelegate(Mapper mapper) { + return new Annotation( + getTypeFactory().getType( "jakarta.inject.Named" ), + Collections.singletonList( + new AnnotationElement( AnnotationElementType.STRING, + Collections.singletonList( mapper.getPackageName() + "." + mapper.getName() ) + ) ) + ); + } + + private Annotation inject() { + return new Annotation( getTypeFactory().getType( "jakarta.inject.Inject" ) ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java index c140e4bcb3..7770eff16c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/Jsr330ComponentProcessor.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.processor; @@ -22,8 +9,14 @@ import java.util.Collections; import java.util.List; +import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.util.AnnotationProcessingException; /** * A {@link ModelElementProcessor} which converts the given {@link Mapper} @@ -36,22 +29,22 @@ public class Jsr330ComponentProcessor extends AnnotationBasedComponentModelProcessor { @Override protected String getComponentModelIdentifier() { - return "jsr330"; + return MappingConstantsGem.ComponentModelGem.JSR330; } @Override protected List getTypeAnnotations(Mapper mapper) { if ( mapper.getDecorator() == null ) { - return Collections.singletonList( named() ); + return Arrays.asList( singleton(), named() ); } else { - return Collections.singletonList( namedDelegate( mapper ) ); + return Arrays.asList( singleton(), namedDelegate( mapper ) ); } } @Override - protected List getDecoratorAnnotations() { - return Collections.singletonList( named() ); + protected List getDecoratorAnnotations(Decorator decorator) { + return Arrays.asList( singleton(), named() ); } @Override @@ -69,18 +62,40 @@ protected boolean requiresGenerationOfDecoratorClass() { return true; } + private Annotation singleton() { + return new Annotation( getType( "Singleton" ) ); + } + private Annotation named() { - return new Annotation( getTypeFactory().getType( "javax.inject.Named" ) ); + return new Annotation( getType( "Named" ) ); } private Annotation namedDelegate(Mapper mapper) { return new Annotation( - getTypeFactory().getType( "javax.inject.Named" ), - Collections.singletonList( '"' + mapper.getPackageName() + "." + mapper.getName() + '"' ) + getType( "Named" ), + Collections.singletonList( + new AnnotationElement( + AnnotationElementType.STRING, + Collections.singletonList( mapper.getPackageName() + "." + mapper.getName() ) + ) ) ); } private Annotation inject() { - return new Annotation( getTypeFactory().getType( "javax.inject.Inject" ) ); + return new Annotation( getType( "Inject" ) ); + } + + private Type getType(String simpleName) { + if ( getTypeFactory().isTypeAvailable( "javax.inject." + simpleName ) ) { + return getTypeFactory().getType( "javax.inject." + simpleName ); + } + + if ( getTypeFactory().isTypeAvailable( "jakarta.inject." + simpleName ) ) { + return getTypeFactory().getType( "jakarta.inject." + simpleName ); + } + + throw new AnnotationProcessingException( + "Couldn't find any of the JSR330 or Jakarta Dependency Inject types." + + " Are you missing a dependency on your classpath?" ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java new file mode 100644 index 0000000000..d3384f0b4c --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperAnnotatedFormattingMessenger.java @@ -0,0 +1,143 @@ +/* + * 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.processor; + +import java.util.Objects; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.TypeUtils; + +/** + * Handles redirection of errors/warnings so that they're shown on the mapper instead of hidden on a superclass. + * + * @author Ben Zegveld + */ +public class MapperAnnotatedFormattingMessenger implements FormattingMessager { + + private FormattingMessager delegateMessager; + private TypeElement mapperTypeElement; + private TypeUtils typeUtils; + + public MapperAnnotatedFormattingMessenger(FormattingMessager delegateMessager, TypeElement mapperTypeElement, + TypeUtils typeUtils) { + this.delegateMessager = delegateMessager; + this.mapperTypeElement = mapperTypeElement; + this.typeUtils = typeUtils; + } + + @Override + public void printMessage(Message msg, Object... args) { + delegateMessager.printMessage( msg, args ); + } + + @Override + public void printMessage(Element e, Message msg, Object... args) { + delegateMessager + .printMessage( + determineDelegationElement( e ), + determineDelegationMessage( e, msg ), + determineDelegationArguments( e, msg, args ) ); + } + + @Override + public void printMessage(Element e, AnnotationMirror a, Message msg, Object... args) { + delegateMessager + .printMessage( + determineDelegationElement( e ), + a, + determineDelegationMessage( e, msg ), + determineDelegationArguments( e, msg, args ) ); + } + + @Override + public void printMessage(Element e, AnnotationMirror a, AnnotationValue v, Message msg, Object... args) { + delegateMessager + .printMessage( + determineDelegationElement( e ), + a, + v, + determineDelegationMessage( e, msg ), + determineDelegationArguments( e, msg, args ) ); + } + + @Override + public void note(int level, Message log, Object... args) { + delegateMessager.note( level, log, args ); + } + + @Override + public boolean isErroneous() { + return delegateMessager.isErroneous(); + } + + private Object[] determineDelegationArguments(Element e, Message msg, Object[] args) { + if ( methodInMapperClass( e ) ) { + return args; + } + String originalMessage = String.format( msg.getDescription(), args ); + return new Object[] { originalMessage, constructMethod( e ), e.getEnclosingElement().getSimpleName() }; + } + + /** + * ExecutableElement.toString() has different values depending on the compiler. Constructing the method itself + * manually will ensure that the message is always traceable to it's source. + */ + private String constructMethod(Element e) { + if ( e instanceof ExecutableElement ) { + ExecutableElement ee = (ExecutableElement) e; + StringBuilder method = new StringBuilder(); + method.append( typeMirrorToString( ee.getReturnType() ) ); + method.append( " " ); + method.append( ee.getSimpleName() ); + method.append( "(" ); + method.append( ee.getParameters() + .stream() + .map( this::parameterToString ) + .collect( Collectors.joining( ", " ) ) ); + method.append( ")" ); + return method.toString(); + } + return e.toString(); + } + + private String parameterToString(VariableElement element) { + return typeMirrorToString( element.asType() ) + " " + element.getSimpleName(); + } + + private String typeMirrorToString(TypeMirror type) { + Element element = typeUtils.asElement( type ); + return element != null ? element.getSimpleName().toString() : Objects.toString( type ); + } + + private Message determineDelegationMessage(Element e, Message msg) { + if ( methodInMapperClass( e ) ) { + return msg; + } + if ( msg.getDiagnosticKind() == Kind.ERROR ) { + return Message.MESSAGE_MOVED_TO_MAPPER_ERROR; + } + return Message.MESSAGE_MOVED_TO_MAPPER_WARNING; + } + + private Element determineDelegationElement(Element e) { + return methodInMapperClass( e ) ? e : mapperTypeElement; + } + + private boolean methodInMapperClass(Element e) { + return mapperTypeElement == null || e.equals( mapperTypeElement ) + || e.getEnclosingElement().equals( mapperTypeElement ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 7baf98730e..db16d52106 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -1,66 +1,78 @@ -/** - * Copyright 2012-2015 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.processor; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; - +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; +import org.mapstruct.ap.internal.gem.DecoratedWithGem; +import org.mapstruct.ap.internal.gem.InheritConfigurationGem; +import org.mapstruct.ap.internal.gem.InheritInverseConfigurationGem; +import org.mapstruct.ap.internal.gem.JavadocGem; +import org.mapstruct.ap.internal.gem.MapperGem; +import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem; +import org.mapstruct.ap.internal.gem.NullValueMappingStrategyGem; +import org.mapstruct.ap.internal.model.AdditionalAnnotationsBuilder; +import org.mapstruct.ap.internal.model.Annotation; import org.mapstruct.ap.internal.model.BeanMappingMethod; +import org.mapstruct.ap.internal.model.ContainerMappingMethod; +import org.mapstruct.ap.internal.model.ContainerMappingMethodBuilder; import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.DefaultMapperReference; import org.mapstruct.ap.internal.model.DelegatingMethod; -import org.mapstruct.ap.internal.model.EnumMappingMethod; +import org.mapstruct.ap.internal.model.Field; import org.mapstruct.ap.internal.model.IterableMappingMethod; +import org.mapstruct.ap.internal.model.Javadoc; import org.mapstruct.ap.internal.model.MapMappingMethod; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.MapperReference; import org.mapstruct.ap.internal.model.MappingBuilderContext; import org.mapstruct.ap.internal.model.MappingMethod; +import org.mapstruct.ap.internal.model.StreamMappingMethod; +import org.mapstruct.ap.internal.model.SupportingConstructorFragment; +import org.mapstruct.ap.internal.model.ValueMappingMethod; +import org.mapstruct.ap.internal.model.common.FormattingParameters; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.model.source.MappingOptions; +import org.mapstruct.ap.internal.model.source.MapperOptions; +import org.mapstruct.ap.internal.model.source.MappingMethodOptions; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SourceMethod; import org.mapstruct.ap.internal.option.Options; -import org.mapstruct.ap.internal.prism.DecoratedWithPrism; -import org.mapstruct.ap.internal.prism.InheritConfigurationPrism; -import org.mapstruct.ap.internal.prism.InheritInverseConfigurationPrism; -import org.mapstruct.ap.internal.prism.MapperPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.processor.creation.MappingResolverImpl; +import org.mapstruct.ap.internal.util.AccessorNamingUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.version.VersionInformation; -import static org.mapstruct.ap.internal.prism.MappingInheritanceStrategyPrism.AUTO_INHERIT_FROM_CONFIG; +import static javax.lang.model.element.Modifier.FINAL; +import static javax.lang.model.element.Modifier.PUBLIC; +import static javax.lang.model.element.Modifier.STATIC; +import static org.mapstruct.ap.internal.model.SupportingConstructorFragment.addAllFragmentsIn; +import static org.mapstruct.ap.internal.model.SupportingField.addAllFieldsIn; import static org.mapstruct.ap.internal.util.Collections.first; import static org.mapstruct.ap.internal.util.Collections.join; @@ -72,46 +84,64 @@ */ public class MapperCreationProcessor implements ModelElementProcessor, Mapper> { - private Elements elementUtils; - private Types typeUtils; + /** Modifiers for public "constant" e.g. "public static final" */ + private static final List PUBLIC_CONSTANT_MODIFIERS = Arrays.asList( PUBLIC, STATIC, FINAL ); + + private ElementUtils elementUtils; + private TypeUtils typeUtils; private FormattingMessager messager; private Options options; private VersionInformation versionInformation; private TypeFactory typeFactory; + private AccessorNamingUtils accessorNaming; private MappingBuilderContext mappingContext; + private AdditionalAnnotationsBuilder additionalAnnotationsBuilder; + @Override public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List sourceModel) { this.elementUtils = context.getElementUtils(); this.typeUtils = context.getTypeUtils(); - this.messager = context.getMessager(); + this.messager = + new MapperAnnotatedFormattingMessenger( context.getMessager(), mapperTypeElement, context.getTypeUtils() ); this.options = context.getOptions(); this.versionInformation = context.getVersionInformation(); this.typeFactory = context.getTypeFactory(); + this.accessorNaming = context.getAccessorNaming(); + additionalAnnotationsBuilder = + new AdditionalAnnotationsBuilder( elementUtils, typeFactory, messager ); - MapperConfiguration mapperConfig = MapperConfiguration.getInstanceOn( mapperTypeElement ); - List mapperReferences = initReferencedMappers( mapperTypeElement, mapperConfig ); + MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() ); + List mapperReferences = initReferencedMappers( mapperTypeElement, mapperOptions ); MappingBuilderContext ctx = new MappingBuilderContext( typeFactory, elementUtils, typeUtils, messager, + versionInformation, + accessorNaming, + context.getNullabilityResolver(), + context.getEnumMappingStrategy(), + context.getEnumTransformationStrategies(), options, new MappingResolverImpl( messager, elementUtils, typeUtils, typeFactory, - sourceModel, - mapperReferences + new ArrayList<>( sourceModel ), + mapperReferences, + options.isVerbose() ), mapperTypeElement, - sourceModel, + //sourceModel is passed only to fetch the after/before mapping methods in lifecycleCallbackFactory; + //Consider removing those methods directly into MappingBuilderContext. + Collections.unmodifiableList( sourceModel ), mapperReferences ); this.mappingContext = ctx; - return getMapper( mapperTypeElement, mapperConfig, sourceModel ); + return getMapper( mapperTypeElement, mapperOptions, sourceModel ); } @Override @@ -119,14 +149,15 @@ public int getPriority() { return 1000; } - private List initReferencedMappers(TypeElement element, MapperConfiguration mapperConfig) { - List result = new LinkedList(); - List variableNames = new LinkedList(); + private List initReferencedMappers(TypeElement element, MapperOptions mapperAnnotation) { + List result = new LinkedList<>(); + List variableNames = new LinkedList<>(); - for ( TypeMirror usedMapper : mapperConfig.uses() ) { + for ( TypeMirror usedMapper : mapperAnnotation.uses() ) { DefaultMapperReference mapperReference = DefaultMapperReference.getInstance( typeFactory.getType( usedMapper ), - MapperPrism.getInstanceOn( typeUtils.asElement( usedMapper ) ) != null, + MapperGem.instanceOn( typeUtils.asElement( usedMapper ) ) != null, + hasSingletonInstance( usedMapper ), typeFactory, variableNames ); @@ -138,45 +169,90 @@ private List initReferencedMappers(TypeElement element, MapperC return result; } - private Mapper getMapper(TypeElement element, MapperConfiguration mapperConfig, List methods) { - List mapperReferences = mappingContext.getMapperReferences(); - List mappingMethods = getMappingMethods( mapperConfig, methods ); - mappingMethods.addAll( mappingContext.getUsedVirtualMappings() ); + private boolean hasSingletonInstance(TypeMirror mapper) { + return typeUtils.asElement( mapper ).getEnclosedElements().stream() + .anyMatch( a -> isPublicConstantOfType( a, "INSTANCE", mapper ) ); + } + + /** + * @return true if the element is a "public static final" field (e.g. a constant) + * named fieldName of type "fieldType" + */ + private boolean isPublicConstantOfType(Element element, String fieldName, TypeMirror fieldType) { + return element.getKind().isField() && + element.getModifiers().containsAll( PUBLIC_CONSTANT_MODIFIERS ) && + element.getSimpleName().contentEquals( fieldName ) && + typeUtils.isSameType( element.asType(), fieldType ); + } + + private Mapper getMapper(TypeElement element, MapperOptions mapperOptions, List methods) { + + List mappingMethods = getMappingMethods( mapperOptions, methods ); + mappingMethods.addAll( mappingContext.getUsedSupportedMappings() ); mappingMethods.addAll( mappingContext.getMappingsToGenerate() ); + // handle fields + List fields = new ArrayList<>( mappingContext.getMapperReferences() ); + Set supportingFieldSet = new LinkedHashSet<>(mappingContext.getUsedSupportedFields()); + addAllFieldsIn( mappingContext.getUsedSupportedMappings(), supportingFieldSet ); + fields.addAll( supportingFieldSet ); + + // handle constructor fragments + Set constructorFragments = new LinkedHashSet<>(); + addAllFragmentsIn( mappingContext.getUsedSupportedMappings(), constructorFragments ); + Mapper mapper = new Mapper.Builder() .element( element ) - .mappingMethods( mappingMethods ) - .mapperReferences( mapperReferences ) + .methods( mappingMethods ) + .fields( fields ) + .constructorFragments( constructorFragments ) .options( options ) .versionInformation( versionInformation ) - .decorator( getDecorator( element, methods, mapperConfig.implementationName(), - mapperConfig.implementationPackage() ) ) + .decorator( getDecorator( element, methods, mapperOptions ) ) .typeFactory( typeFactory ) .elementUtils( elementUtils ) - .extraImports( getExtraImports( element ) ) - .implName( mapperConfig.implementationName() ) - .implPackage( mapperConfig.implementationPackage() ) + .extraImports( getExtraImports( element, mapperOptions ) ) + .implName( mapperOptions.implementationName() ) + .implPackage( mapperOptions.implementationPackage() ) + .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) + .additionalAnnotations( additionalAnnotationsBuilder.getProcessedAnnotations( element ) ) + .javadoc( getJavadoc( element ) ) + .classAccessibility( mapperOptions.accessibility() ) .build(); + if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) { + messager.printMessage( element, Message.GENERAL_NOT_ALL_FORGED_CREATED, + mappingContext.getForgedMethodsUnderCreation().keySet() ); + } + + if ( element.getModifiers().contains( Modifier.PRIVATE ) ) { + // If the mapper element is private then we should report an error + // we can't generate an implementation for a private mapper + mappingContext.getMessager() + .printMessage( element, + Message.GENERAL_CANNOT_IMPLEMENT_PRIVATE_MAPPER, + element.getSimpleName().toString(), + element.getKind() == ElementKind.INTERFACE ? "interface" : "class" + ); + } + return mapper; } - private Decorator getDecorator(TypeElement element, List methods, String implName, - String implPackage) { - DecoratedWithPrism decoratorPrism = DecoratedWithPrism.getInstanceOn( element ); + private Decorator getDecorator(TypeElement element, List methods, MapperOptions mapperOptions) { + DecoratedWithGem decoratedWith = DecoratedWithGem.instanceOn( element ); - if ( decoratorPrism == null ) { + if ( decoratedWith == null ) { return null; } - TypeElement decoratorElement = (TypeElement) typeUtils.asElement( decoratorPrism.value() ); + TypeElement decoratorElement = (TypeElement) typeUtils.asElement( decoratedWith.value().get() ); if ( !typeUtils.isAssignable( decoratorElement.asType(), element.asType() ) ) { - messager.printMessage( element, decoratorPrism.mirror, Message.DECORATOR_NO_SUBTYPE); + messager.printMessage( element, decoratedWith.mirror(), Message.DECORATOR_NO_SUBTYPE ); } - List mappingMethods = new ArrayList( methods.size() ); + List mappingMethods = new ArrayList<>( methods.size() ); for ( SourceMethod mappingMethod : methods ) { boolean implementationRequired = true; @@ -187,7 +263,7 @@ private Decorator getDecorator(TypeElement element, List methods, } } Type declaringMapper = mappingMethod.getDeclaringMapper(); - if ( implementationRequired && !( mappingMethod.isDefault() || mappingMethod.isStatic()) ) { + if ( implementationRequired && !( mappingMethod.isDefault() || mappingMethod.isStatic() ) ) { if ( ( declaringMapper == null ) || declaringMapper.equals( typeFactory.getType( element ) ) ) { mappingMethods.add( new DelegatingMethod( mappingMethod ) ); } @@ -211,82 +287,71 @@ else if ( constructor.getParameters().size() == 1 ) { } if ( !hasDelegateConstructor && !hasDefaultConstructor ) { - messager.printMessage( element, decoratorPrism.mirror, Message.DECORATOR_CONSTRUCTOR ); + messager.printMessage( element, decoratedWith.mirror(), Message.DECORATOR_CONSTRUCTOR ); } + // Get annotations from the decorator class + Set decoratorAnnotations = additionalAnnotationsBuilder.getProcessedAnnotations( decoratorElement ); + Decorator decorator = new Decorator.Builder() - .elementUtils( elementUtils ) - .typeFactory( typeFactory ) - .mapperElement( element ) - .decoratorPrism( decoratorPrism ) - .methods( mappingMethods ) - .hasDelegateConstructor( hasDelegateConstructor ) - .options( options ) - .versionInformation( versionInformation ) - .implName( implName ) - .implPackage( implPackage ) - .extraImports( getExtraImports( element ) ) - .build(); + .elementUtils( elementUtils ) + .typeFactory( typeFactory ) + .mapperElement( element ) + .decoratedWith( decoratedWith ) + .methods( mappingMethods ) + .hasDelegateConstructor( hasDelegateConstructor ) + .options( options ) + .versionInformation( versionInformation ) + .implName( mapperOptions.implementationName() ) + .implPackage( mapperOptions.implementationPackage() ) + .extraImports( getExtraImports( element, mapperOptions ) ) + .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) + .additionalAnnotations( decoratorAnnotations ) + .build(); return decorator; } - private SortedSet getExtraImports(TypeElement element) { - SortedSet extraImports = new TreeSet(); + private SortedSet getExtraImports(TypeElement element, MapperOptions mapperOptions) { + SortedSet extraImports = new TreeSet<>(); - MapperConfiguration mapperConfiguration = MapperConfiguration.getInstanceOn( element ); - for ( TypeMirror extraImport : mapperConfiguration.imports() ) { - Type type = typeFactory.getType( extraImport ); + for ( TypeMirror extraImport : mapperOptions.imports() ) { + Type type = typeFactory.getAlwaysImportedType( extraImport ); extraImports.add( type ); } // Add original package if a dest package has been set - if ( !"default".equals( mapperConfiguration.implementationPackage() ) ) { + if ( !"default".equals( mapperOptions.implementationPackage() ) ) { extraImports.add( typeFactory.getType( element ) ); } return extraImports; } - private List getMappingMethods(MapperConfiguration mapperConfig, List methods) { - List mappingMethods = new ArrayList(); + private List getMappingMethods(MapperOptions mapperAnnotation, List methods) { + List mappingMethods = new ArrayList<>(); for ( SourceMethod method : methods ) { if ( !method.overridesMethod() ) { continue; } - mergeInheritedOptions( method, mapperConfig, methods, new ArrayList() ); + mergeInheritedOptions( method, mapperAnnotation, methods, new ArrayList<>(), null ); - MappingOptions mappingOptions = method.getMappingOptions(); + MappingMethodOptions mappingOptions = method.getOptions(); boolean hasFactoryMethod = false; if ( method.isIterableMapping() ) { + this.messager.note( 1, Message.ITERABLEMAPPING_CREATE_NOTE, method ); - IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder(); - String dateFormat = null; - List qualifiers = null; - TypeMirror qualifyingElementTargetType = null; - NullValueMappingStrategyPrism nullValueMappingStrategy = null; - - if ( mappingOptions.getIterableMapping() != null ) { - dateFormat = mappingOptions.getIterableMapping().getDateFormat(); - qualifiers = mappingOptions.getIterableMapping().getQualifiers(); - qualifyingElementTargetType = mappingOptions.getIterableMapping().getQualifyingElementTargetType(); - nullValueMappingStrategy = mappingOptions.getIterableMapping().getNullValueMappingStrategy(); - } - - IterableMappingMethod iterableMappingMethod = builder - .mappingContext( mappingContext ) - .method( method ) - .dateFormat( dateFormat ) - .qualifiers( qualifiers ) - .qualifyingElementTargetType( qualifyingElementTargetType ) - .nullValueMappingStrategy( nullValueMappingStrategy ) - .build(); + IterableMappingMethod iterableMappingMethod = createWithElementMappingMethod( + method, + mappingOptions, + new IterableMappingMethod.Builder() + ); hasFactoryMethod = iterableMappingMethod.getFactoryMethod() != null; mappingMethods.add( iterableMappingMethod ); @@ -295,73 +360,78 @@ else if ( method.isMapMapping() ) { MapMappingMethod.Builder builder = new MapMappingMethod.Builder(); - String keyDateFormat = null; - String valueDateFormat = null; - List keyQualifiers = null; - List valueQualifiers = null; - TypeMirror keyQualifyingTargetType = null; - TypeMirror valueQualifyingTargetType = null; - NullValueMappingStrategyPrism nullValueMappingStrategy = null; + SelectionParameters keySelectionParameters = null; + FormattingParameters keyFormattingParameters = null; + SelectionParameters valueSelectionParameters = null; + FormattingParameters valueFormattingParameters = null; + NullValueMappingStrategyGem nullValueMappingStrategy = null; if ( mappingOptions.getMapMapping() != null ) { - keyDateFormat = mappingOptions.getMapMapping().getKeyFormat(); - valueDateFormat = mappingOptions.getMapMapping().getValueFormat(); - keyQualifiers = mappingOptions.getMapMapping().getKeyQualifiers(); - valueQualifiers = mappingOptions.getMapMapping().getValueQualifiers(); - keyQualifyingTargetType = mappingOptions.getMapMapping().getKeyQualifyingTargetType(); - valueQualifyingTargetType = mappingOptions.getMapMapping().getValueQualifyingTargetType(); + keySelectionParameters = mappingOptions.getMapMapping().getKeySelectionParameters(); + keyFormattingParameters = mappingOptions.getMapMapping().getKeyFormattingParameters(); + valueSelectionParameters = mappingOptions.getMapMapping().getValueSelectionParameters(); + valueFormattingParameters = mappingOptions.getMapMapping().getValueFormattingParameters(); nullValueMappingStrategy = mappingOptions.getMapMapping().getNullValueMappingStrategy(); } + this.messager.note( 1, Message.MAPMAPPING_CREATE_NOTE, method ); MapMappingMethod mapMappingMethod = builder .mappingContext( mappingContext ) .method( method ) - .keyDateFormat( keyDateFormat ) - .valueDateFormat( valueDateFormat ) - .keyQualifiers( keyQualifiers ) - .valueQualifiers( valueQualifiers ) - .keyQualifyingTargetType( keyQualifyingTargetType ) - .valueQualifyingTargetType( valueQualifyingTargetType ) - .nullValueMappingStrategy( nullValueMappingStrategy ) + .keyFormattingParameters( keyFormattingParameters ) + .keySelectionParameters( keySelectionParameters ) + .valueFormattingParameters( valueFormattingParameters ) + .valueSelectionParameters( valueSelectionParameters ) .build(); hasFactoryMethod = mapMappingMethod.getFactoryMethod() != null; mappingMethods.add( mapMappingMethod ); } - else if ( method.isEnumMapping() ) { - - EnumMappingMethod.Builder builder = new EnumMappingMethod.Builder(); - MappingMethod enumMappingMethod = builder + else if ( method.isValueMapping() ) { + // prefer value mappings over enum mapping + this.messager.note( 1, Message.VALUEMAPPING_CREATE_NOTE, method ); + ValueMappingMethod valueMappingMethod = new ValueMappingMethod.Builder() .mappingContext( mappingContext ) - .souceMethod( method ) + .method( method ) + .valueMappings( mappingOptions.getValueMappings() ) + .enumMapping( mappingOptions.getEnumMappingOptions() ) .build(); - if ( enumMappingMethod != null ) { - mappingMethods.add( enumMappingMethod ); + if ( valueMappingMethod != null ) { + mappingMethods.add( valueMappingMethod ); } } + else if ( method.isRemovedEnumMapping() ) { + messager.printMessage( + method.getExecutable(), + Message.ENUMMAPPING_REMOVED + ); + } + else if ( method.isStreamMapping() ) { + this.messager.note( 1, Message.STREAMMAPPING_CREATE_NOTE, method ); + StreamMappingMethod streamMappingMethod = createWithElementMappingMethod( + method, + mappingOptions, + new StreamMappingMethod.Builder() + ); + + // If we do StreamMapping that means that internally there is a way to generate the result type + hasFactoryMethod = + streamMappingMethod.getFactoryMethod() != null || method.getResultType().isStreamType(); + mappingMethods.add( streamMappingMethod ); + } else { - - NullValueMappingStrategyPrism nullValueMappingStrategy = null; - TypeMirror resultType = null; - List qualifiers = null; - - if ( mappingOptions.getBeanMapping() != null ) { - nullValueMappingStrategy = mappingOptions.getBeanMapping().getNullValueMappingStrategy(); - resultType = mappingOptions.getBeanMapping().getResultType(); - qualifiers = mappingOptions.getBeanMapping().getQualifiers(); - } - BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder(); - BeanMappingMethod beanMappingMethod = builder + this.messager.note( 1, Message.BEANMAPPING_CREATE_NOTE, method ); + BeanMappingMethod.Builder beanMappingBuilder = new BeanMappingMethod.Builder(); + BeanMappingMethod beanMappingMethod = beanMappingBuilder .mappingContext( mappingContext ) - .souceMethod( method ) - .nullValueMappingStrategy( nullValueMappingStrategy ) - .qualifiers( qualifiers ) - .resultType( resultType ) + .sourceMethod( method ) .build(); + // We can consider that the bean mapping method can always be constructed. If there is a problem + // it would have been reported in its build + hasFactoryMethod = true; if ( beanMappingMethod != null ) { - hasFactoryMethod = beanMappingMethod.getFactoryMethod() != null; mappingMethods.add( beanMappingMethod ); } } @@ -376,8 +446,45 @@ else if ( method.isEnumMapping() ) { return mappingMethods; } - private void mergeInheritedOptions(SourceMethod method, MapperConfiguration mapperConfig, - List availableMethods, List initializingMethods) { + private Javadoc getJavadoc(TypeElement element) { + JavadocGem javadocGem = JavadocGem.instanceOn( element ); + + if ( javadocGem == null || !isConsistent( javadocGem, element, messager ) ) { + return null; + } + + Javadoc javadoc = new Javadoc.Builder() + .value( javadocGem.value().getValue() ) + .authors( javadocGem.authors().getValue() ) + .deprecated( javadocGem.deprecated().getValue() ) + .since( javadocGem.since().getValue() ) + .build(); + + return javadoc; + } + + private M createWithElementMappingMethod(SourceMethod method, + MappingMethodOptions mappingMethodOptions, ContainerMappingMethodBuilder builder) { + + FormattingParameters formattingParameters = null; + SelectionParameters selectionParameters = null; + + if ( mappingMethodOptions.getIterableMapping() != null ) { + formattingParameters = mappingMethodOptions.getIterableMapping().getFormattingParameters(); + selectionParameters = mappingMethodOptions.getIterableMapping().getSelectionParameters(); + } + + return builder + .mappingContext( mappingContext ) + .method( method ) + .formattingParameters( formattingParameters ) + .selectionParameters( selectionParameters ) + .build(); + } + + private void mergeInheritedOptions(SourceMethod method, MapperOptions mapperConfig, + List availableMethods, List initializingMethods, + AnnotationMirror annotationMirror) { if ( initializingMethods.contains( method ) ) { // cycle detected @@ -392,46 +499,74 @@ private void mergeInheritedOptions(SourceMethod method, MapperConfiguration mapp initializingMethods.add( method ); - MappingOptions mappingOptions = method.getMappingOptions(); - List applicablePrototypeMethods = method.getApplicablePrototypeMethods(); + MappingMethodOptions mappingOptions = method.getOptions(); + List applicableReversePrototypeMethods = method.getApplicableReversePrototypeMethods(); - MappingOptions inverseMappingOptions = - getInverseMappingOptions( availableMethods, method, initializingMethods, mapperConfig ); + SourceMethod inverseTemplateMethod = + getInverseTemplateMethod( join( availableMethods, applicableReversePrototypeMethods ), + method, + initializingMethods, + mapperConfig ); - MappingOptions templateMappingOptions = - getTemplateMappingOptions( + List applicablePrototypeMethods = method.getApplicablePrototypeMethods(); + + SourceMethod forwardTemplateMethod = + getForwardTemplateMethod( join( availableMethods, applicablePrototypeMethods ), method, initializingMethods, mapperConfig ); - if ( templateMappingOptions != null ) { - mappingOptions.applyInheritedOptions( templateMappingOptions, false, method, messager, typeFactory ); + // apply defined (@InheritConfiguration, @InheritInverseConfiguration) mappings + if ( forwardTemplateMethod != null ) { + mappingOptions.applyInheritedOptions( method, forwardTemplateMethod, false, annotationMirror ); } - else if ( inverseMappingOptions != null ) { - mappingOptions.applyInheritedOptions( inverseMappingOptions, true, method, messager, typeFactory ); + if ( inverseTemplateMethod != null ) { + mappingOptions.applyInheritedOptions( method, inverseTemplateMethod, true, annotationMirror ); } - else if ( mapperConfig.getMappingInheritanceStrategy() == AUTO_INHERIT_FROM_CONFIG ) { - if ( applicablePrototypeMethods.size() == 1 ) { - mappingOptions.applyInheritedOptions( - first( applicablePrototypeMethods ).getMappingOptions(), - false, - method, - messager, - typeFactory ); + + // apply auto inherited options + MappingInheritanceStrategyGem inheritanceStrategy = mapperConfig.getMappingInheritanceStrategy(); + if ( inheritanceStrategy.isAutoInherit() ) { + + // but.. there should not be an @InheritedConfiguration + if ( forwardTemplateMethod == null && inheritanceStrategy.isApplyForward() ) { + if ( applicablePrototypeMethods.size() == 1 ) { + mappingOptions.applyInheritedOptions( method, first( applicablePrototypeMethods ), false, + annotationMirror ); + } + else if ( applicablePrototypeMethods.size() > 1 ) { + messager.printMessage( + method.getExecutable(), + Message.INHERITCONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH, + Strings.join( applicablePrototypeMethods, ", " ) ); + } } - else if ( applicablePrototypeMethods.size() > 1 ) { - messager.printMessage( - method.getExecutable(), - Message.INHERITCONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH, - Strings.join( applicablePrototypeMethods, ", " ) ); + + // or no @InheritInverseConfiguration + if ( inverseTemplateMethod == null && inheritanceStrategy.isApplyReverse() ) { + if ( applicableReversePrototypeMethods.size() == 1 ) { + mappingOptions.applyInheritedOptions( method, first( applicableReversePrototypeMethods ), true, + annotationMirror ); + } + else if ( applicableReversePrototypeMethods.size() > 1 ) { + messager.printMessage( + method.getExecutable(), + Message.INHERITINVERSECONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH, + Strings.join( applicableReversePrototypeMethods, ", " ) ); + } } } + // @BeanMapping( ignoreByDefault = true ) + if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().isIgnoredByDefault() ) { + mappingOptions.applyIgnoreAll( method, typeFactory, mappingContext.getMessager() ); + } + mappingOptions.markAsFullyInitialized(); } - private void reportErrorIfNoImplementationTypeIsRegisteredForInterfaceReturnType(SourceMethod method) { + private void reportErrorIfNoImplementationTypeIsRegisteredForInterfaceReturnType(Method method) { if ( method.getReturnType().getTypeMirror().getKind() != TypeKind.VOID && method.getReturnType().isInterface() && method.getReturnType().getImplementationType() == null ) { @@ -444,25 +579,24 @@ private void reportErrorIfNoImplementationTypeIsRegisteredForInterfaceReturnType * {@code @InheritInverseConfiguration} and exactly one such configuring method can unambiguously be selected (as * per the source/target type and optionally the name given via {@code @InheritInverseConfiguration}). */ - private MappingOptions getInverseMappingOptions(List rawMethods, SourceMethod method, - List initializingMethods, - MapperConfiguration mapperConfig) { + private SourceMethod getInverseTemplateMethod(List rawMethods, SourceMethod method, + List initializingMethods, + MapperOptions mapperConfig) { SourceMethod resultMethod = null; - InheritInverseConfigurationPrism reversePrism = InheritInverseConfigurationPrism.getInstanceOn( - method.getExecutable() - ); + InheritInverseConfigurationGem inverseConfiguration = + InheritInverseConfigurationGem.instanceOn( method.getExecutable() ); - if ( reversePrism != null ) { + if ( inverseConfiguration != null ) { - // method is configured as being reverse method, collect candidates - List candidates = new ArrayList(); + // method is configured as being inverse method, collect candidates + List candidates = new ArrayList<>(); for ( SourceMethod oneMethod : rawMethods ) { - if ( oneMethod.reverses( method ) ) { + if ( method.inverses( oneMethod ) ) { candidates.add( oneMethod ); } } - String name = reversePrism.name(); + String name = inverseConfiguration.name().get(); if ( candidates.size() == 1 ) { // no ambiguity: if no configuredBy is specified, or configuredBy specified and match if ( name.isEmpty() ) { @@ -472,13 +606,13 @@ else if ( candidates.get( 0 ).getName().equals( name ) ) { resultMethod = candidates.get( 0 ); } else { - reportErrorWhenNonMatchingName( candidates.get( 0 ), method, reversePrism ); + reportErrorWhenNonMatchingName( candidates.get( 0 ), method, inverseConfiguration ); } } else if ( candidates.size() > 1 ) { // ambiguity: find a matching method that matches configuredBy - List nameFilteredcandidates = new ArrayList(); + List nameFilteredcandidates = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { if ( candidate.getName().equals( name ) ) { nameFilteredcandidates.add( candidate ); @@ -489,28 +623,34 @@ else if ( candidates.size() > 1 ) { resultMethod = nameFilteredcandidates.get( 0 ); } else if ( nameFilteredcandidates.size() > 1 ) { - reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, reversePrism ); + reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, inverseConfiguration ); } - - if ( resultMethod == null ) { - reportErrorWhenAmbigousReverseMapping( candidates, method, reversePrism ); + else { + reportErrorWhenAmbiguousReverseMapping( candidates, method, inverseConfiguration ); } } } - return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods ); + return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods, + getAnnotationMirror( inverseConfiguration ) ); + } + + private AnnotationMirror getAnnotationMirror(InheritInverseConfigurationGem inverseConfiguration) { + return inverseConfiguration == null ? null : inverseConfiguration.mirror(); } - private MappingOptions extractInitializedOptions(SourceMethod resultMethod, + private SourceMethod extractInitializedOptions(SourceMethod resultMethod, List rawMethods, - MapperConfiguration mapperConfig, - List initializingMethods) { + MapperOptions mapperConfig, + List initializingMethods, + AnnotationMirror annotationMirror) { if ( resultMethod != null ) { - if ( !resultMethod.getMappingOptions().isFullyInitialized() ) { - mergeInheritedOptions( resultMethod, mapperConfig, rawMethods, initializingMethods ); + if ( !resultMethod.getOptions().isFullyInitialized() ) { + mergeInheritedOptions( resultMethod, mapperConfig, rawMethods, initializingMethods, + annotationMirror ); } - return resultMethod.getMappingOptions(); + return resultMethod; } return null; @@ -520,20 +660,18 @@ private MappingOptions extractInitializedOptions(SourceMethod resultMethod, * Returns the configuring forward method's options in case the given method is annotated with * {@code @InheritConfiguration} and exactly one such configuring method can unambiguously be selected (as per the * source/target type and optionally the name given via {@code @InheritConfiguration}). The method cannot be marked - * forward mapping itself (hence 'ohter'). And neither can it contain an {@code @InheritReverseConfiguration} + * forward mapping itself (hence 'other'). And neither can it contain an {@code @InheritReverseConfiguration} */ - private MappingOptions getTemplateMappingOptions(List rawMethods, SourceMethod method, - List initializingMethods, - MapperConfiguration mapperConfig) { + private SourceMethod getForwardTemplateMethod(List rawMethods, SourceMethod method, + List initializingMethods, + MapperOptions mapperConfig) { SourceMethod resultMethod = null; - InheritConfigurationPrism forwardPrism = InheritConfigurationPrism.getInstanceOn( - method.getExecutable() - ); + InheritConfigurationGem inheritConfiguration = + InheritConfigurationGem.instanceOn( method.getExecutable() ); - if (forwardPrism != null) { - reportErrorWhenInheritForwardAlsoHasInheritReverseMapping( method ); + if ( inheritConfiguration != null ) { - List candidates = new ArrayList(); + List candidates = new ArrayList<>(); for ( SourceMethod oneMethod : rawMethods ) { // method must be similar but not equal if ( method.canInheritFrom( oneMethod ) && !( oneMethod.equals( method ) ) ) { @@ -541,7 +679,7 @@ private MappingOptions getTemplateMappingOptions(List rawMethods, } } - String name = forwardPrism.name(); + String name = inheritConfiguration.name().get(); if ( candidates.size() == 1 ) { // no ambiguity: if no configuredBy is specified, or configuredBy specified and match SourceMethod sourceMethod = first( candidates ); @@ -552,57 +690,51 @@ else if ( sourceMethod.getName().equals( name ) ) { resultMethod = sourceMethod; } else { - reportErrorWhenNonMatchingName( sourceMethod, method, forwardPrism ); + reportErrorWhenNonMatchingName( sourceMethod, method, inheritConfiguration ); } } else if ( candidates.size() > 1 ) { // ambiguity: find a matching method that matches configuredBy - List nameFilteredcandidates = new ArrayList(); + List nameFilteredCandidates = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { if ( candidate.getName().equals( name ) ) { - nameFilteredcandidates.add( candidate ); + nameFilteredCandidates.add( candidate ); } } - if ( nameFilteredcandidates.size() == 1 ) { - resultMethod = first( nameFilteredcandidates ); + if ( nameFilteredCandidates.size() == 1 ) { + resultMethod = first( nameFilteredCandidates ); } - else if ( nameFilteredcandidates.size() > 1 ) { - reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, forwardPrism ); + else if ( nameFilteredCandidates.size() > 1 ) { + reportErrorWhenSeveralNamesMatch( nameFilteredCandidates, method, inheritConfiguration ); } - - if ( resultMethod == null ) { - reportErrorWhenAmbigousMapping( candidates, method, forwardPrism ); + else { + reportErrorWhenAmbiguousMapping( candidates, method, inheritConfiguration ); } } } - return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods ); + return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods, + getAnnotationMirror( inheritConfiguration ) ); } - private void reportErrorWhenInheritForwardAlsoHasInheritReverseMapping(SourceMethod method) { - InheritInverseConfigurationPrism reversePrism = InheritInverseConfigurationPrism.getInstanceOn( - method.getExecutable() - ); - if ( reversePrism != null ) { - messager.printMessage( method.getExecutable(), reversePrism.mirror, Message.INHERITCONFIGURATION_BOTH ); - } - + private AnnotationMirror getAnnotationMirror(InheritConfigurationGem inheritConfiguration) { + return inheritConfiguration == null ? null : inheritConfiguration.mirror(); } - private void reportErrorWhenAmbigousReverseMapping(List candidates, SourceMethod method, - InheritInverseConfigurationPrism reversePrism) { + private void reportErrorWhenAmbiguousReverseMapping(List candidates, SourceMethod method, + InheritInverseConfigurationGem inverseGem) { - List candidateNames = new ArrayList(); + List candidateNames = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { candidateNames.add( candidate.getName() ); } - String name = reversePrism.name(); + String name = inverseGem.name().get(); if ( name.isEmpty() ) { messager.printMessage( method.getExecutable(), - reversePrism.mirror, + inverseGem.mirror(), Message.INHERITINVERSECONFIGURATION_DUPLICATES, Strings.join( candidateNames, "(), " ) @@ -610,7 +742,7 @@ private void reportErrorWhenAmbigousReverseMapping(List candidates } else { messager.printMessage( method.getExecutable(), - reversePrism.mirror, + inverseGem.mirror(), Message.INHERITINVERSECONFIGURATION_INVALID_NAME, Strings.join( candidateNames, "(), " ), name @@ -620,47 +752,48 @@ private void reportErrorWhenAmbigousReverseMapping(List candidates } private void reportErrorWhenSeveralNamesMatch(List candidates, SourceMethod method, - InheritInverseConfigurationPrism reversePrism) { + InheritInverseConfigurationGem inverseGem) { messager.printMessage( method.getExecutable(), - reversePrism.mirror, + inverseGem.mirror(), Message.INHERITINVERSECONFIGURATION_DUPLICATE_MATCHES, - reversePrism.name(), + inverseGem.name().get(), Strings.join( candidates, ", " ) ); } private void reportErrorWhenNonMatchingName(SourceMethod onlyCandidate, SourceMethod method, - InheritInverseConfigurationPrism reversePrism) { + InheritInverseConfigurationGem inverseGem) { messager.printMessage( method.getExecutable(), - reversePrism.mirror, + inverseGem.mirror(), Message.INHERITINVERSECONFIGURATION_NO_NAME_MATCH, - reversePrism.name(), + inverseGem.name().get(), onlyCandidate.getName() ); } - private void reportErrorWhenAmbigousMapping(List candidates, SourceMethod method, - InheritConfigurationPrism prism) { + private void reportErrorWhenAmbiguousMapping(List candidates, SourceMethod method, + InheritConfigurationGem gem) { - List candidateNames = new ArrayList(); + List candidateNames = new ArrayList<>(); for ( SourceMethod candidate : candidates ) { candidateNames.add( candidate.getName() ); } - String name = prism.name(); + String name = gem.name().get(); if ( name.isEmpty() ) { messager.printMessage( method.getExecutable(), - prism.mirror, + gem.mirror(), Message.INHERITCONFIGURATION_DUPLICATES, Strings.join( candidateNames, "(), " ) ); } else { - messager.printMessage( method.getExecutable(), - prism.mirror, + messager.printMessage( + method.getExecutable(), + gem.mirror(), Message.INHERITCONFIGURATION_INVALIDNAME, Strings.join( candidateNames, "(), " ), name @@ -669,24 +802,37 @@ private void reportErrorWhenAmbigousMapping(List candidates, Sourc } private void reportErrorWhenSeveralNamesMatch(List candidates, SourceMethod method, - InheritConfigurationPrism prism) { + InheritConfigurationGem gem) { - messager.printMessage( method.getExecutable(), - prism.mirror, + messager.printMessage( + method.getExecutable(), + gem.mirror(), Message.INHERITCONFIGURATION_DUPLICATE_MATCHES, - prism.name(), - Strings.join( candidates, "(), " ) + gem.name().get(), + Strings.join( candidates, ", " ) ); } private void reportErrorWhenNonMatchingName(SourceMethod onlyCandidate, SourceMethod method, - InheritConfigurationPrism prims) { + InheritConfigurationGem gem) { - messager.printMessage( method.getExecutable(), - prims.mirror, + messager.printMessage( + method.getExecutable(), + gem.mirror(), Message.INHERITCONFIGURATION_NO_NAME_MATCH, - prims.name(), + gem.name().get(), onlyCandidate.getName() ); } + + private boolean isConsistent( JavadocGem gem, TypeElement element, FormattingMessager messager ) { + if ( !gem.value().hasValue() + && !gem.authors().hasValue() + && !gem.deprecated().hasValue() + && !gem.since().hasValue() ) { + messager.printMessage( element, gem.mirror(), Message.JAVADOC_NO_ELEMENTS ); + return false; + } + return true; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperRenderingProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperRenderingProcessor.java index 0dee856050..bab6af7b09 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperRenderingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperRenderingProcessor.java @@ -1,25 +1,11 @@ -/** - * Copyright 2012-2015 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.processor; import java.io.IOException; - import javax.annotation.processing.Filer; import javax.lang.model.element.TypeElement; import javax.tools.JavaFileObject; @@ -39,29 +25,34 @@ public class MapperRenderingProcessor implements ModelElementProcessor> { + private static final String MAPPING_FQN = "org.mapstruct.Mapping"; + private static final String MAPPINGS_FQN = "org.mapstruct.Mappings"; + private static final String SUB_CLASS_MAPPING_FQN = "org.mapstruct.SubclassMapping"; + private static final String SUB_CLASS_MAPPINGS_FQN = "org.mapstruct.SubclassMappings"; + private static final String VALUE_MAPPING_FQN = "org.mapstruct.ValueMapping"; + private static final String VALUE_MAPPINGS_FQN = "org.mapstruct.ValueMappings"; + private static final String CONDITION_FQN = "org.mapstruct.Condition"; + private static final String IGNORED_FQN = "org.mapstruct.Ignored"; + private static final String IGNORED_LIST_FQN = "org.mapstruct.IgnoredList"; private FormattingMessager messager; private TypeFactory typeFactory; - private Types typeUtils; - private Elements elementUtils; + private AccessorNamingUtils accessorNaming; + private Map enumTransformationStrategies; + private TypeUtils typeUtils; + private ElementUtils elementUtils; + private Options options; @Override public List process(ProcessorContext context, TypeElement mapperTypeElement, Void sourceModel) { this.messager = context.getMessager(); this.typeFactory = context.getTypeFactory(); + this.accessorNaming = context.getAccessorNaming(); this.typeUtils = context.getTypeUtils(); this.elementUtils = context.getElementUtils(); + this.enumTransformationStrategies = context.getEnumTransformationStrategies(); + this.options = context.getOptions(); + + this.messager.note( 0, Message.PROCESSING_NOTE, mapperTypeElement ); + + MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() ); - MapperConfiguration mapperConfig = MapperConfiguration.getInstanceOn( mapperTypeElement ); + if ( mapperOptions.hasMapperConfig() ) { + this.messager.note( 0, Message.CONFIG_NOTE, mapperOptions.mapperConfigType().asElement().getSimpleName() ); + } - if ( !mapperConfig.isValid() ) { + if ( !mapperOptions.isValid() ) { throw new AnnotationProcessingException( "Couldn't retrieve @Mapper annotation", mapperTypeElement, - mapperConfig.getAnnotationMirror() ); + mapperOptions.getAnnotationMirror() ); } - List prototypeMethods = - retrievePrototypeMethods( mapperConfig.getMapperConfigMirror(), mapperConfig ); - return retrieveMethods( mapperTypeElement, mapperTypeElement, mapperConfig, prototypeMethods ); + List prototypeMethods = retrievePrototypeMethods( mapperTypeElement, mapperOptions ); + return retrieveMethods( + typeFactory.getType( mapperTypeElement.asType() ), + mapperTypeElement, + mapperOptions, + prototypeMethods + ); } @Override @@ -95,16 +127,22 @@ public int getPriority() { return 1; } - private List retrievePrototypeMethods(TypeMirror typeMirror, MapperConfiguration mapperConfig ) { - if ( typeMirror == null || typeMirror.getKind() == TypeKind.VOID ) { + private List retrievePrototypeMethods(TypeElement mapperTypeElement, + MapperOptions mapperAnnotation) { + if ( !mapperAnnotation.hasMapperConfig() ) { return Collections.emptyList(); } - TypeElement typeElement = asTypeElement( typeMirror ); - List methods = new ArrayList(); - for ( ExecutableElement executable : getAllEnclosedExecutableElements( elementUtils, typeElement ) ) { + TypeElement typeElement = asTypeElement( mapperAnnotation.mapperConfigType() ); + List methods = new ArrayList<>(); + for ( ExecutableElement executable : elementUtils.getAllEnclosedExecutableElements( typeElement ) ) { + + if ( executable.isDefault() || executable.getModifiers().contains( Modifier.STATIC ) ) { + // skip the default and static methods because these are not prototypes. + continue; + } - ExecutableType methodType = typeFactory.getMethodType( typeElement, executable ); + ExecutableType methodType = typeFactory.getMethodType( mapperAnnotation.mapperConfigType(), executable ); List parameters = typeFactory.getParameters( methodType, executable ); boolean containsTargetTypeParameter = SourceMethod.containsTargetTypeParameter( parameters ); @@ -117,8 +155,9 @@ private List retrievePrototypeMethods(TypeMirror typeMirror, Mappe executable, parameters, containsTargetTypeParameter, - mapperConfig, - prototypeMethods + mapperAnnotation, + prototypeMethods, + mapperTypeElement ); if ( method != null ) { @@ -132,22 +171,24 @@ private List retrievePrototypeMethods(TypeMirror typeMirror, Mappe /** * Retrieves the mapping methods declared by the given mapper type. * - * @param usedMapper The type of interest (either the mapper to implement or a used mapper via @uses annotation) + * @param usedMapperType The type of interest (either the mapper to implement, a used mapper via @uses annotation, + * or a parameter type annotated with @Context) * @param mapperToImplement the top level type (mapper) that requires implementation - * @param mapperConfig the mapper config + * @param mapperOptions the mapper config * @param prototypeMethods prototype methods defined in mapper config type * @return All mapping methods declared by the given type */ - private List retrieveMethods(TypeElement usedMapper, TypeElement mapperToImplement, - MapperConfiguration mapperConfig, List prototypeMethods) { - List methods = new ArrayList(); + private List retrieveMethods(Type usedMapperType, TypeElement mapperToImplement, + MapperOptions mapperOptions, List prototypeMethods) { + List methods = new ArrayList<>(); - for ( ExecutableElement executable : getAllEnclosedExecutableElements( elementUtils, usedMapper ) ) { + TypeElement usedMapper = usedMapperType.getTypeElement(); + for ( ExecutableElement executable : elementUtils.getAllEnclosedExecutableElements( usedMapper ) ) { SourceMethod method = getMethod( - usedMapper, + usedMapperType, executable, mapperToImplement, - mapperConfig, + mapperOptions, prototypeMethods ); if ( method != null ) { @@ -157,29 +198,40 @@ private List retrieveMethods(TypeElement usedMapper, TypeElement m //Add all methods of used mappers in order to reference them in the aggregated model if ( usedMapper.equals( mapperToImplement ) ) { - for ( TypeMirror mapper : mapperConfig.uses() ) { - methods.addAll( retrieveMethods( - asTypeElement( mapper ), - mapperToImplement, - mapperConfig, - prototypeMethods ) ); + for ( DeclaredType mapper : mapperOptions.uses() ) { + TypeElement usesMapperElement = asTypeElement( mapper ); + if ( !mapperToImplement.equals( usesMapperElement ) ) { + methods.addAll( retrieveMethods( + typeFactory.getType( mapper ), + mapperToImplement, + mapperOptions, + prototypeMethods ) ); + } + else { + messager.printMessage( + mapperToImplement, + mapperOptions.getAnnotationMirror(), + Message.RETRIEVAL_MAPPER_USES_CYCLE, + mapperToImplement + ); + } } } return methods; } - private TypeElement asTypeElement(TypeMirror usedMapper) { - return (TypeElement) ( (DeclaredType) usedMapper ).asElement(); + private TypeElement asTypeElement(DeclaredType type) { + return (TypeElement) type.asElement(); } - private SourceMethod getMethod(TypeElement usedMapper, + private SourceMethod getMethod(Type usedMapperType, ExecutableElement method, TypeElement mapperToImplement, - MapperConfiguration mapperConfig, + MapperOptions mapperOptions, List prototypeMethods) { - - ExecutableType methodType = typeFactory.getMethodType( usedMapper, method ); + TypeElement usedMapper = usedMapperType.getTypeElement(); + ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapperType.getTypeMirror(), method ); List parameters = typeFactory.getParameters( methodType, method ); Type returnType = typeFactory.getReturnType( methodType ); @@ -192,12 +244,14 @@ private SourceMethod getMethod(TypeElement usedMapper, method, parameters, containsTargetTypeParameter, - mapperConfig, - prototypeMethods ); - } - //otherwise add reference to existing mapper method - else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( parameters, returnType ) - || isValidLifecycleCallbackMethod( method, returnType ) ) { + mapperOptions, + prototypeMethods, + mapperToImplement ); + } + // otherwise add reference to existing mapper method + else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( method, parameters, returnType ) + || isValidLifecycleCallbackMethod( method ) + || isValidPresenceCheckMethod( method, parameters, returnType ) ) { return getReferencedMethod( usedMapper, methodType, method, mapperToImplement, parameters ); } else { @@ -206,13 +260,15 @@ else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( paramet } private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, ExecutableElement method, - List parameters, - boolean containsTargetTypeParameter, - MapperConfiguration mapperConfig, - List prototypeMethods ) { + List parameters, + boolean containsTargetTypeParameter, + MapperOptions mapperOptions, + List prototypeMethods, + TypeElement mapperToImplement) { Type returnType = typeFactory.getReturnType( methodType ); List exceptionTypes = typeFactory.getThrownTypes( methodType ); - List sourceParameters = extractSourceParameters( parameters ); + List sourceParameters = Parameter.getSourceParameters( parameters ); + List contextParameters = Parameter.getContextParameters( parameters ); Parameter targetParameter = extractTargetParameter( parameters ); Type resultType = selectResultType( returnType, targetParameter ); @@ -220,6 +276,7 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, method, sourceParameters, targetParameter, + contextParameters, resultType, returnType, containsTargetTypeParameter @@ -229,31 +286,103 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, return null; } + ParameterProvidedMethods contextProvidedMethods = + retrieveContextProvidedMethods( contextParameters, mapperToImplement, mapperOptions ); + + BeanMappingOptions beanMappingOptions = BeanMappingOptions.getInstanceOn( + BeanMappingGem.instanceOn( method ), + mapperOptions, + method, + messager, + typeUtils, + typeFactory ); + + Set mappingOptions = getMappings( method, beanMappingOptions ); + + IterableMappingOptions iterableMappingOptions = IterableMappingOptions.fromGem( + IterableMappingGem.instanceOn( method ), + mapperOptions, + method, + messager, + typeUtils + ); + + MapMappingOptions mapMappingOptions = MapMappingOptions.fromGem( + MapMappingGem.instanceOn( method ), + mapperOptions, + method, + messager, + typeUtils + ); + + EnumMappingOptions enumMappingOptions = EnumMappingOptions.getInstanceOn( + method, + mapperOptions, + enumTransformationStrategies, + messager + ); + + // We want to get as much error reporting as possible. + // If targetParameter is not null it means we have an update method + SubclassValidator subclassValidator = new SubclassValidator( messager, typeUtils ); + Set subclassMappingOptions = getSubclassMappings( + sourceParameters, + targetParameter != null ? null : resultType, + method, + beanMappingOptions, + subclassValidator + ); + return new SourceMethod.Builder() - .setExecutable( method ) - .setParameters( parameters ) - .setReturnType( returnType ) - .setExceptionTypes( exceptionTypes ) - .setMappings( getMappings( method ) ) - .setIterableMapping( - IterableMapping.fromPrism( - IterableMappingPrism.getInstanceOn( method ), method, messager - ) - ) - .setMapMapping( - MapMapping.fromPrism( MapMappingPrism.getInstanceOn( method ), method, messager ) - ) - .setBeanMapping( - BeanMapping.fromPrism( BeanMappingPrism.getInstanceOn( method ), method, messager ) - ) - .setTypeUtils( typeUtils ) - .setMessager( messager ) - .setTypeFactory( typeFactory ) - .setMapperConfiguration( mapperConfig ) - .setPrototypeMethods( prototypeMethods ) + .setExecutable( method ) + .setParameters( parameters ) + .setReturnType( returnType ) + .setExceptionTypes( exceptionTypes ) + .setMapper( mapperOptions ) + .setBeanMappingOptions( beanMappingOptions ) + .setMappingOptions( mappingOptions ) + .setIterableMappingOptions( iterableMappingOptions ) + .setMapMappingOptions( mapMappingOptions ) + .setValueMappingOptionss( getValueMappings( method ) ) + .setEnumMappingOptions( enumMappingOptions ) + .setSubclassMappings( subclassMappingOptions ) + .setSubclassValidator( subclassValidator ) + .setTypeUtils( typeUtils ) + .setTypeFactory( typeFactory ) + .setPrototypeMethods( prototypeMethods ) + .setContextProvidedMethods( contextProvidedMethods ) + .setVerboseLogging( options.isVerbose() ) .build(); } + private ParameterProvidedMethods retrieveContextProvidedMethods( + List contextParameters, TypeElement mapperToImplement, MapperOptions mapperConfig) { + + ParameterProvidedMethods.Builder builder = ParameterProvidedMethods.builder(); + for ( Parameter contextParam : contextParameters ) { + if ( contextParam.getType().isPrimitive() || contextParam.getType().isArrayType() ) { + continue; + } + List contextParamMethods = retrieveMethods( + contextParam.getType(), + mapperToImplement, + mapperConfig, + Collections.emptyList() ); + + List contextProvidedMethods = new ArrayList<>( contextParamMethods.size() ); + for ( SourceMethod sourceMethod : contextParamMethods ) { + if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory() + || sourceMethod.getConditionOptions().isAnyStrategyApplicable() ) { + contextProvidedMethods.add( sourceMethod ); + } + } + + builder.addMethodsForParameter( contextParam, contextProvidedMethods ); + } + + return builder.build(); + } + private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableType methodType, ExecutableElement method, TypeElement mapperToImplement, List parameters) { @@ -271,32 +400,71 @@ private SourceMethod getReferencedMethod(TypeElement usedMapper, ExecutableType return new SourceMethod.Builder() .setDeclaringMapper( usedMapper.equals( mapperToImplement ) ? null : usedMapperAsType ) - .setDefininingType( definingType ) + .setDefiningType( definingType ) .setExecutable( method ) .setParameters( parameters ) .setReturnType( returnType ) .setExceptionTypes( exceptionTypes ) .setTypeUtils( typeUtils ) .setTypeFactory( typeFactory ) + .setConditionOptions( getConditionOptions( method, parameters ) ) + .setVerboseLogging( options.isVerbose() ) .build(); } - private boolean isValidLifecycleCallbackMethod(ExecutableElement method, Type returnType) { - return isVoid( returnType ) && Executables.isLifecycleCallbackMethod( method ); + private boolean isValidLifecycleCallbackMethod(ExecutableElement method) { + return Executables.isLifecycleCallbackMethod( method ); } private boolean isValidReferencedMethod(List parameters) { return isValidReferencedOrFactoryMethod( 1, 1, parameters ); } - private boolean isValidFactoryMethod(List parameters, Type returnType) { - return !isVoid( returnType ) && isValidReferencedOrFactoryMethod( 0, 0, parameters ); + private boolean isValidFactoryMethod(ExecutableElement method, List parameters, Type returnType) { + return !isVoid( returnType ) + && ( isValidReferencedOrFactoryMethod( 0, 0, parameters ) || hasFactoryAnnotation( method ) ); + } + + private boolean hasFactoryAnnotation(ExecutableElement method) { + return ObjectFactoryGem.instanceOn( method ) != null; + } + + private boolean isValidPresenceCheckMethod(ExecutableElement method, List parameters, Type returnType) { + for ( Parameter param : parameters ) { + + if ( param.isSourcePropertyName() && !param.getType().isString() ) { + messager.printMessage( + param.getElement(), + SourcePropertyNameGem.instanceOn( param.getElement() ).mirror(), + Message.RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE + ); + return false; + } + + if ( param.isTargetPropertyName() && !param.getType().isString() ) { + messager.printMessage( + param.getElement(), + TargetPropertyNameGem.instanceOn( param.getElement() ).mirror(), + Message.RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE + ); + return false; + } + } + return isBoolean( returnType ) && hasConditionAnnotation( method ); + } + + private boolean hasConditionAnnotation(ExecutableElement method) { + return ConditionGem.instanceOn( method ) != null; } private boolean isVoid(Type returnType) { return returnType.getTypeMirror().getKind() == TypeKind.VOID; } + private boolean isBoolean(Type returnType) { + return Boolean.class.getCanonicalName().equals( returnType.getBoxedEquivalent().getFullyQualifiedName() ); + } + private boolean isValidReferencedOrFactoryMethod(int sourceParamCount, int targetParamCount, List parameters) { int validSourceParameters = 0; @@ -307,20 +475,17 @@ private boolean isValidReferencedOrFactoryMethod(int sourceParamCount, int targe if ( param.isMappingTarget() ) { targetParameters++; } - - if ( param.isTargetType() ) { + else if ( param.isTargetType() ) { targetTypeParameters++; } - - if ( !param.isMappingTarget() && !param.isTargetType() ) { + else if ( !param.isMappingContext() ) { validSourceParameters++; } } return validSourceParameters == sourceParamCount && targetParameters <= targetParamCount - && targetTypeParameters <= 1 - && parameters.size() == validSourceParameters + targetParameters + targetTypeParameters; + && targetTypeParameters <= 1; } private Parameter extractTargetParameter(List parameters) { @@ -333,17 +498,6 @@ private Parameter extractTargetParameter(List parameters) { return null; } - private List extractSourceParameters(List parameters) { - List sourceParameters = new ArrayList( parameters.size() ); - for ( Parameter param : parameters ) { - if ( !param.isMappingTarget() ) { - sourceParameters.add( param ); - } - } - - return sourceParameters; - } - private Type selectResultType(Type returnType, Parameter targetParameter) { if ( null != targetParameter ) { return targetParameter.getType(); @@ -354,14 +508,15 @@ private Type selectResultType(Type returnType, Parameter targetParameter) { } private boolean checkParameterAndReturnType(ExecutableElement method, List sourceParameters, - Parameter targetParameter, Type resultType, Type returnType, - boolean containsTargetTypeParameter) { + Parameter targetParameter, List contextParameters, + Type resultType, Type returnType, boolean containsTargetTypeParameter) { if ( sourceParameters.isEmpty() ) { messager.printMessage( method, Message.RETRIEVAL_NO_INPUT_ARGS ); return false; } - if ( targetParameter != null && ( sourceParameters.size() + 1 != method.getParameters().size() ) ) { + if ( targetParameter != null + && ( sourceParameters.size() + contextParameters.size() + 1 != method.getParameters().size() ) ) { messager.printMessage( method, Message.RETRIEVAL_DUPLICATE_MAPPING_TARGETS ); return false; } @@ -372,57 +527,75 @@ private boolean checkParameterAndReturnType(ExecutableElement method, List contextParameterTypes = new HashSet<>(); + for ( Parameter contextParameter : contextParameters ) { + if ( !contextParameterTypes.add( contextParameter.getType() ) ) { + messager.printMessage( method, Message.RETRIEVAL_CONTEXT_PARAMS_WITH_SAME_TYPE ); + return false; + } + } + if ( returnType.isTypeVar() || resultType.isTypeVar() ) { messager.printMessage( method, Message.RETRIEVAL_TYPE_VAR_RESULT ); return false; } - Type parameterType = sourceParameters.get( 0 ).getType(); + if ( sourceParameters.size() == 1 ) { + Type parameterType = sourceParameters.get( 0 ).getType(); - if ( parameterType.isIterableType() && !resultType.isIterableType() ) { - messager.printMessage( method, Message.RETRIEVAL_ITERABLE_TO_NON_ITERABLE ); - return false; - } + if ( isStreamTypeOrIterableFromJavaStdLib( parameterType ) && !resultType.isIterableOrStreamType() ) { + messager.printMessage( method, Message.RETRIEVAL_ITERABLE_TO_NON_ITERABLE ); + return false; + } - if ( containsTargetTypeParameter ) { - messager.printMessage( method, Message.RETRIEVAL_MAPPING_HAS_TARGET_TYPE_PARAMETER ); - return false; - } + if ( !parameterType.isIterableOrStreamType() && isStreamTypeOrIterableFromJavaStdLib( resultType ) ) { + messager.printMessage( method, Message.RETRIEVAL_NON_ITERABLE_TO_ITERABLE ); + return false; + } - if ( !parameterType.isIterableType() && resultType.isIterableType() ) { - messager.printMessage( method, Message.RETRIEVAL_NON_ITERABLE_TO_ITERABLE ); - return false; - } + if ( !parameterType.isIterableOrStreamType() && resultType.isArrayType() ) { + messager.printMessage( method, Message.RETRIEVAL_NON_ITERABLE_TO_ARRAY ); + return false; + } - if ( parameterType.isPrimitive() ) { - messager.printMessage( method, Message.RETRIEVAL_PRIMITIVE_PARAMETER ); - return false; - } + if ( parameterType.isPrimitive() ) { + messager.printMessage( method, Message.RETRIEVAL_PRIMITIVE_PARAMETER ); + return false; + } - if ( resultType.isPrimitive() ) { - messager.printMessage( method, Message.RETRIEVAL_PRIMITIVE_RETURN ); - return false; + for ( Type typeParameter : parameterType.getTypeParameters() ) { + if ( typeParameter.hasSuperBound() ) { + messager.printMessage( method, Message.RETRIEVAL_WILDCARD_SUPER_BOUND_SOURCE ); + return false; + } + + if ( typeParameter.isTypeVar() ) { + messager.printMessage( method, Message.RETRIEVAL_TYPE_VAR_SOURCE ); + return false; + } + } } - if ( parameterType.isEnumType() && !resultType.isEnumType() ) { - messager.printMessage( method, Message.RETRIEVAL_ENUM_TO_NON_ENUM ); + if ( containsTargetTypeParameter ) { + messager.printMessage( method, Message.RETRIEVAL_MAPPING_HAS_TARGET_TYPE_PARAMETER ); return false; } - if ( !parameterType.isEnumType() && resultType.isEnumType() ) { - messager.printMessage( method, Message.RETRIEVAL_NON_ENUM_TO_ENUM ); + if ( resultType.isPrimitive() ) { + messager.printMessage( method, Message.RETRIEVAL_PRIMITIVE_RETURN ); return false; } @@ -431,55 +604,289 @@ private boolean checkParameterAndReturnType(ExecutableElement method, List getMappings(ExecutableElement method, BeanMappingOptions beanMapping) { + Set processedAnnotations = new RepeatableMappings( beanMapping ) + .getProcessedAnnotations( method ); + processedAnnotations.addAll( new IgnoredConditions( processedAnnotations ) + .getProcessedAnnotations( method ) ); + return processedAnnotations; + } + + /** + * Retrieves the subclass mappings configured via {@code @SubclassMapping} from the given method. + * + * @param method The method of interest + * @param beanMapping options coming from bean mapping method + * + * @return The subclass mappings for the given method + */ + private Set getSubclassMappings(List sourceParameters, Type resultType, + ExecutableElement method, BeanMappingOptions beanMapping, + SubclassValidator validator) { + return new RepeatableSubclassMappings( beanMapping, sourceParameters, resultType, validator ) + .getProcessedAnnotations( method ); + } + + /** + * Retrieves the conditions configured via {@code @Condition} from the given method. + * + * @param method The method of interest + * @param parameters + * @return The condition options for the given method + */ + + private Set getConditionOptions(ExecutableElement method, List parameters) { + return new MetaConditions( parameters ).getProcessedAnnotations( method ); + } + + private class RepeatableMappings extends RepeatableAnnotations { + private BeanMappingOptions beanMappingOptions; + + RepeatableMappings(BeanMappingOptions beanMappingOptions) { + super( elementUtils, MAPPING_FQN, MAPPINGS_FQN ); + this.beanMappingOptions = beanMappingOptions; + } + + @Override + protected MappingGem singularInstanceOn(Element element) { + return MappingGem.instanceOn( element ); + } + + @Override + protected MappingsGem multipleInstanceOn(Element element) { + return MappingsGem.instanceOn( element ); + } + + @Override + protected void addInstance(MappingGem gem, Element method, Set mappings) { + MappingOptions.addInstance( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings ); + } + + @Override + protected void addInstances(MappingsGem gem, Element method, Set mappings) { + MappingOptions.addInstances( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings ); + } + } + + private class RepeatableSubclassMappings + extends RepeatableAnnotations { + private final List sourceParameters; + private final Type resultType; + private SubclassValidator validator; + private BeanMappingOptions beanMappingOptions; + + RepeatableSubclassMappings(BeanMappingOptions beanMappingOptions, List sourceParameters, + Type resultType, SubclassValidator validator) { + super( elementUtils, SUB_CLASS_MAPPING_FQN, SUB_CLASS_MAPPINGS_FQN ); + this.beanMappingOptions = beanMappingOptions; + this.sourceParameters = sourceParameters; + this.resultType = resultType; + this.validator = validator; + } + + @Override + protected SubclassMappingGem singularInstanceOn(Element element) { + return SubclassMappingGem.instanceOn( element ); + } + + @Override + protected SubclassMappingsGem multipleInstanceOn(Element element) { + return SubclassMappingsGem.instanceOn( element ); + } + + @Override + protected void addInstance(SubclassMappingGem gem, + Element method, + Set mappings) { + SubclassMappingOptions.addInstance( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType, + validator ); + } + + @Override + protected void addInstances(SubclassMappingsGem gem, + Element method, + Set mappings) { + SubclassMappingOptions.addInstances( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings, + sourceParameters, + resultType, + validator ); + } + } + /** - * Retrieves the mappings configured via {@code @Mapping} from the given + * Retrieves the mappings configured via {@code @ValueMapping} from the given * method. * * @param method The method of interest * * @return The mappings for the given method, keyed by target property name */ - private Map> getMappings(ExecutableElement method) { - Map> mappings = new HashMap>(); + private List getValueMappings(ExecutableElement method) { + Set processedAnnotations = new RepeatValueMappings().getProcessedAnnotations( method ); + return new ArrayList<>(processedAnnotations); + } - MappingPrism mappingAnnotation = MappingPrism.getInstanceOn( method ); - MappingsPrism mappingsAnnotation = MappingsPrism.getInstanceOn( method ); + private class RepeatValueMappings + extends RepeatableAnnotations { - if ( mappingAnnotation != null ) { - if ( !mappings.containsKey( mappingAnnotation.target() ) ) { - mappings.put( mappingAnnotation.target(), new ArrayList() ); - } - Mapping mapping = Mapping.fromMappingPrism( mappingAnnotation, method, messager ); - if ( mapping != null ) { - mappings.get( mappingAnnotation.target() ).add( mapping ); + protected RepeatValueMappings() { + super( elementUtils, VALUE_MAPPING_FQN, VALUE_MAPPINGS_FQN ); + } + + @Override + protected ValueMappingGem singularInstanceOn(Element element) { + return ValueMappingGem.instanceOn( element ); + } + + @Override + protected ValueMappingsGem multipleInstanceOn(Element element) { + return ValueMappingsGem.instanceOn( element ); + } + + @Override + protected void addInstance(ValueMappingGem gem, Element source, Set mappings) { + ValueMappingOptions valueMappingOptions = ValueMappingOptions.fromMappingGem( gem ); + mappings.add( valueMappingOptions ); + } + + @Override + protected void addInstances(ValueMappingsGem gems, Element source, Set mappings) { + ValueMappingOptions.fromMappingsGem( gems, (ExecutableElement) source, messager, mappings ); + } + } + + private class MetaConditions extends MetaAnnotations { + + protected final List parameters; + + protected MetaConditions(List parameters) { + super( elementUtils, CONDITION_FQN ); + this.parameters = parameters; + } + + @Override + protected ConditionGem instanceOn(Element element) { + return ConditionGem.instanceOn( element ); + } + + @Override + protected void addInstance(ConditionGem gem, Element source, Set values) { + ConditionOptions options = ConditionOptions.getInstanceOn( + gem, + (ExecutableElement) source, + parameters, + messager + ); + if ( options != null ) { + values.add( options ); } } + } + + private class IgnoredConditions extends RepeatableAnnotations { + + protected final Set processedAnnotations; - if ( mappingsAnnotation != null ) { - mappings.putAll( Mapping.fromMappingsPrism( mappingsAnnotation, method, messager ) ); + protected IgnoredConditions( Set processedAnnotations ) { + super( elementUtils, IGNORED_FQN, IGNORED_LIST_FQN ); + this.processedAnnotations = processedAnnotations; } - return mappings; + @Override + protected IgnoredGem singularInstanceOn(Element element) { + return IgnoredGem.instanceOn( element ); + } + + @Override + protected IgnoredListGem multipleInstanceOn(Element element) { + return IgnoredListGem.instanceOn( element ); + } + + @Override + protected void addInstance(IgnoredGem gem, Element method, Set mappings) { + IgnoredGem ignoredGem = IgnoredGem.instanceOn( method ); + if ( ignoredGem == null ) { + ignoredGem = gem; + } + String prefix = ignoredGem.prefix().get(); + for ( String target : ignoredGem.targets().get() ) { + String realTarget = target; + if ( !prefix.isEmpty() ) { + realTarget = prefix + "." + target; + } + MappingOptions mappingOptions = MappingOptions.forIgnore( realTarget ); + if ( processedAnnotations.contains( mappingOptions ) || mappings.contains( mappingOptions ) ) { + messager.printMessage( method, Message.PROPERTYMAPPING_DUPLICATE_TARGETS, realTarget ); + } + else { + mappings.add( mappingOptions ); + } + } + } + + @Override + protected void addInstances(IgnoredListGem gem, Element method, Set mappings) { + IgnoredListGem ignoredListGem = IgnoredListGem.instanceOn( method ); + for ( IgnoredGem ignoredGem : ignoredListGem.value().get() ) { + addInstance( ignoredGem, method, mappings ); + } + } } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java index bfe8691954..10328c4437 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/ModelElementProcessor.java @@ -1,33 +1,25 @@ -/** - * Copyright 2012-2015 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.processor; +import java.util.Map; import javax.annotation.processing.Filer; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.util.AccessorNamingUtils; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.NullabilityResolver; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.version.VersionInformation; +import org.mapstruct.ap.spi.EnumMappingStrategy; +import org.mapstruct.ap.spi.EnumTransformationStrategy; /** * A processor which performs one task of the mapper generation, e.g. retrieving @@ -44,23 +36,31 @@ public interface ModelElementProcessor { /** * Context object passed to * {@link ModelElementProcessor#process(ProcessorContext, TypeElement, Object)} - * providing access to common infrastructure objects such as {@link Types} + * providing access to common infrastructure objects such as {@link TypeUtils} * etc. * * @author Gunnar Morling */ - public interface ProcessorContext { + interface ProcessorContext { Filer getFiler(); - Types getTypeUtils(); + TypeUtils getTypeUtils(); - Elements getElementUtils(); + ElementUtils getElementUtils(); TypeFactory getTypeFactory(); FormattingMessager getMessager(); + AccessorNamingUtils getAccessorNaming(); + + NullabilityResolver getNullabilityResolver(); + + Map getEnumTransformationStrategies(); + + EnumMappingStrategy getEnumMappingStrategy(); + Options getOptions(); VersionInformation getVersionInformation(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java index 8d1be5b302..c31ac51f06 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/SpringComponentProcessor.java @@ -1,30 +1,31 @@ -/** - * Copyright 2012-2015 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.processor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import org.mapstruct.ap.internal.gem.MappingConstantsGem; import org.mapstruct.ap.internal.model.Annotation; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement; +import org.mapstruct.ap.internal.model.annotation.AnnotationElement.AnnotationElementType; + +import static javax.lang.model.element.ElementKind.PACKAGE; /** * A {@link ModelElementProcessor} which converts the given {@link Mapper} @@ -35,15 +36,21 @@ * @author Andreas Gudian */ public class SpringComponentProcessor extends AnnotationBasedComponentModelProcessor { + + private static final String SPRING_COMPONENT_ANNOTATION = "org.springframework.stereotype.Component"; + private static final String SPRING_PRIMARY_ANNOTATION = "org.springframework.context.annotation.Primary"; + @Override protected String getComponentModelIdentifier() { - return "spring"; + return MappingConstantsGem.ComponentModelGem.SPRING; } @Override protected List getTypeAnnotations(Mapper mapper) { - List typeAnnotations = new ArrayList(); - typeAnnotations.add( component() ); + List typeAnnotations = new ArrayList<>(); + if ( !isAlreadyAnnotatedAsSpringStereotype( mapper ) ) { + typeAnnotations.add( component() ); + } if ( mapper.getDecorator() != null ) { typeAnnotations.add( qualifierDelegate() ); @@ -52,12 +59,37 @@ protected List getTypeAnnotations(Mapper mapper) { return typeAnnotations; } + /** + * Returns the annotations that need to be added to the generated decorator, filtering out any annotations + * that are already present or represented as meta-annotations. + * + * @param decorator the decorator to process + * @return A list of annotations that should be added to the generated decorator. + */ @Override - protected List getDecoratorAnnotations() { - return Arrays.asList( - component(), - primary() - ); + protected List getDecoratorAnnotations(Decorator decorator) { + Set desiredAnnotationNames = new LinkedHashSet<>(); + desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION ); + desiredAnnotationNames.add( SPRING_PRIMARY_ANNOTATION ); + List decoratorAnnotations = decorator.getAnnotations(); + if ( !decoratorAnnotations.isEmpty() ) { + Set handledElements = new HashSet<>(); + for ( Annotation annotation : decoratorAnnotations ) { + removeAnnotationsPresentOnElement( + annotation.getType().getTypeElement(), + desiredAnnotationNames, + handledElements + ); + if ( desiredAnnotationNames.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return Collections.emptyList(); + } + } + } + + return desiredAnnotationNames.stream() + .map( this::createAnnotation ) + .collect( Collectors.toList() ); } @Override @@ -80,21 +112,98 @@ protected boolean requiresGenerationOfDecoratorClass() { return true; } + private Annotation createAnnotation(String canonicalName) { + return new Annotation( getTypeFactory().getType( canonicalName ) ); + } + private Annotation autowired() { - return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Autowired" ) ); + return createAnnotation( "org.springframework.beans.factory.annotation.Autowired" ); } private Annotation qualifierDelegate() { return new Annotation( getTypeFactory().getType( "org.springframework.beans.factory.annotation.Qualifier" ), - Collections.singletonList( "\"delegate\"" ) ); + Collections.singletonList( + new AnnotationElement( + AnnotationElementType.STRING, + Collections.singletonList( "delegate" ) + ) ) ); } - private Annotation primary() { - return new Annotation( getTypeFactory().getType( "org.springframework.context.annotation.Primary" ) ); + private Annotation component() { + return createAnnotation( SPRING_COMPONENT_ANNOTATION ); } - private Annotation component() { - return new Annotation( getTypeFactory().getType( "org.springframework.stereotype.Component" ) ); + private boolean isAlreadyAnnotatedAsSpringStereotype(Mapper mapper) { + Set desiredAnnotationNames = new LinkedHashSet<>(); + desiredAnnotationNames.add( SPRING_COMPONENT_ANNOTATION ); + + List mapperAnnotations = mapper.getAnnotations(); + if ( !mapperAnnotations.isEmpty() ) { + Set handledElements = new HashSet<>(); + for ( Annotation annotation : mapperAnnotations ) { + removeAnnotationsPresentOnElement( + annotation.getType().getTypeElement(), + desiredAnnotationNames, + handledElements + ); + if ( desiredAnnotationNames.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return true; + } + } + } + + return false; + } + + /** + * Removes all the annotations and meta-annotations from {@code annotations} which are on the given element. + * + * @param element the element to check + * @param annotations the annotations to check for + * @param handledElements set of already handled elements to avoid infinite recursion + */ + private void removeAnnotationsPresentOnElement(Element element, Set annotations, + Set handledElements) { + if ( annotations.isEmpty() ) { + return; + } + if ( element instanceof TypeElement && + annotations.remove( ( (TypeElement) element ).getQualifiedName().toString() ) ) { + if ( annotations.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return; + } + } + + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element annotationMirrorElement = annotationMirror.getAnnotationType().asElement(); + // Bypass java lang annotations to improve performance avoiding unnecessary checks + if ( !isAnnotationInPackage( annotationMirrorElement, "java.lang.annotation" ) && + !handledElements.contains( annotationMirrorElement ) ) { + handledElements.add( annotationMirrorElement ); + if ( annotations.remove( ( (TypeElement) annotationMirrorElement ).getQualifiedName().toString() ) ) { + if ( annotations.isEmpty() ) { + // If all annotations are removed, we can stop further processing + return; + } + } + + removeAnnotationsPresentOnElement( element, annotations, handledElements ); + } + } + } + + private PackageElement getPackageOf( Element element ) { + while ( element.getKind() != PACKAGE ) { + element = element.getEnclosingElement(); + } + + return (PackageElement) element; + } + + private boolean isAnnotationInPackage(Element element, String packageFQN) { + return packageFQN.equals( getPackageOf( element ).getQualifiedName().toString() ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 8566fbc093..fb380dc146 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -1,54 +1,71 @@ -/** - * Copyright 2012-2015 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.processor.creation; import java.util.ArrayList; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.Set; - +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; +import javax.lang.model.util.ElementFilter; import org.mapstruct.ap.internal.conversion.ConversionProvider; import org.mapstruct.ap.internal.conversion.Conversions; -import org.mapstruct.ap.internal.model.AssignmentFactory; +import org.mapstruct.ap.internal.gem.ReportingPolicyGem; +import org.mapstruct.ap.internal.model.Field; +import org.mapstruct.ap.internal.model.ForgedMethodHistory; +import org.mapstruct.ap.internal.model.HelperMethod; import org.mapstruct.ap.internal.model.MapperReference; import org.mapstruct.ap.internal.model.MappingBuilderContext.MappingResolver; import org.mapstruct.ap.internal.model.MethodReference; -import org.mapstruct.ap.internal.model.VirtualMappingMethod; -import org.mapstruct.ap.internal.model.assignment.Assignment; +import org.mapstruct.ap.internal.model.SupportingField; +import org.mapstruct.ap.internal.model.SupportingMappingMethod; +import org.mapstruct.ap.internal.model.common.Assignment; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.DefaultConversionContext; +import org.mapstruct.ap.internal.model.common.FieldReference; +import org.mapstruct.ap.internal.model.common.FormattingParameters; +import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.SourceMethod; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMappingMethods; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; +import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; +import org.mapstruct.ap.internal.model.source.selector.SelectionContext; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; -import org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds; +import org.mapstruct.ap.internal.util.MessageConstants; +import org.mapstruct.ap.internal.util.NativeTypes; import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.TypeUtils; + +import static org.mapstruct.ap.internal.util.Collections.first; +import static org.mapstruct.ap.internal.util.Collections.firstKey; +import static org.mapstruct.ap.internal.util.Collections.firstValue; /** * The one and only implementation of {@link MappingResolver}. The class has been split into an interface an @@ -59,26 +76,37 @@ */ public class MappingResolverImpl implements MappingResolver { + private static final int LIMIT_REPORTING_AMBIGUOUS = 5; + private final FormattingMessager messager; - private final Types typeUtils; + private final TypeUtils typeUtils; private final TypeFactory typeFactory; - private final List sourceModel; + private final List sourceModel; private final List mapperReferences; private final Conversions conversions; private final BuiltInMappingMethods builtInMethods; private final MethodSelectors methodSelectors; + private final boolean verboseLogging; + + private static final String JL_OBJECT_NAME = Object.class.getName(); + /** * Private methods which are not present in the original mapper interface and are added to map certain property * types. */ - private final Set usedVirtualMappings = new HashSet(); + private final Set usedSupportedMappings = new LinkedHashSet<>(); - public MappingResolverImpl(FormattingMessager messager, Elements elementUtils, Types typeUtils, - TypeFactory typeFactory, List sourceModel, - List mapperReferences) { + /** + * Private fields which are added to map certain property types. + */ + private final Set usedSupportedFields = new LinkedHashSet<>(); + + public MappingResolverImpl(FormattingMessager messager, ElementUtils elementUtils, TypeUtils typeUtils, + TypeFactory typeFactory, List sourceModel, + List mapperReferences, boolean verboseLogging) { this.messager = messager; this.typeUtils = typeUtils; this.typeFactory = typeFactory; @@ -86,96 +114,106 @@ public MappingResolverImpl(FormattingMessager messager, Elements elementUtils, T this.sourceModel = sourceModel; this.mapperReferences = mapperReferences; - this.conversions = new Conversions( elementUtils, typeFactory ); + this.conversions = new Conversions( typeFactory ); this.builtInMethods = new BuiltInMappingMethods( typeFactory ); - this.methodSelectors = new MethodSelectors( - typeUtils, - elementUtils, - typeFactory - ); + this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager, null ); + + this.verboseLogging = verboseLogging; } @Override - public Assignment getTargetAssignment(Method mappingMethod, String mappedElement, Type sourceType, - Type targetType, String targetPropertyName, String dateFormat, List qualifiers, - TypeMirror resultType, String sourceReference, boolean preferUpdateMapping) { - - SelectionCriteria criteria = - new SelectionCriteria(qualifiers, targetPropertyName, resultType, preferUpdateMapping ); + public Assignment getTargetAssignment(Method mappingMethod, ForgedMethodHistory description, Type targetType, + FormattingParameters formattingParameters, + SelectionCriteria criteria, SourceRHS sourceRHS, + AnnotationMirror positionHint, + Supplier forger) { ResolvingAttempt attempt = new ResolvingAttempt( sourceModel, mappingMethod, - mappedElement, - dateFormat, - sourceReference, - criteria + description, + formattingParameters, + sourceRHS, + criteria, + positionHint, + forger, + builtInMethods.getBuiltInMethods(), + messager, + verboseLogging ); - return attempt.getTargetAssignment( sourceType, targetType ); + return attempt.getTargetAssignment( sourceRHS.getSourceTypeForMatching(), targetType ); } @Override - public Set getUsedVirtualMappings() { - return usedVirtualMappings; + public Set getUsedSupportedMappings() { + return usedSupportedMappings; } @Override - public MethodReference getFactoryMethod( Method mappingMethod, Type targetType, List qualifiers, - TypeMirror resultType ) { - - SelectionCriteria criteria = new SelectionCriteria( qualifiers, null, resultType, false ); - - ResolvingAttempt attempt = new ResolvingAttempt( - sourceModel, - mappingMethod, - null, - null, - null, - criteria - ); + public Set getUsedSupportedFields() { + return usedSupportedFields; + } - SourceMethod matchingSourceMethod = attempt.getBestMatch( sourceModel, null, targetType ); - if ( matchingSourceMethod != null ) { - MapperReference ref = attempt.findMapperReference( matchingSourceMethod ); - return new MethodReference( matchingSourceMethod, ref, null ); + private MapperReference findMapperReference(Method method) { + for ( MapperReference ref : mapperReferences ) { + if ( ref.getType().equals( method.getDeclaringMapper() ) ) { + ref.setUsed( ref.isUsed() || !method.isStatic() ); + ref.setTypeRequiresImport( true ); + return ref; + } } return null; - } private class ResolvingAttempt { private final Method mappingMethod; - private final String mappedElement; - private final List methods; - private final String dateFormat; + private final ForgedMethodHistory description; + private final List methods; private final SelectionCriteria selectionCriteria; - private final String sourceReference; - private final boolean savedPreferUpdateMapping; - - // resolving via 2 steps creates the possibillity of wrong matches, first builtin method matches, - // second doesn't. In that case, the first builtin method should not lead to a virtual method + private final SourceRHS sourceRHS; + private final FormattingParameters formattingParameters; + private final AnnotationMirror positionHint; + private final Supplier forger; + private final List builtIns; + private final FormattingMessager messager; + private final int reportingLimitAmbiguous; + + // resolving via 2 steps creates the possibility of wrong matches, first builtin method matches, + // second doesn't. In that case, the first builtin method should not lead to a supported method // so this set must be cleared. - private final Set virtualMethodCandidates; + private final Set supportingMethodCandidates; - private ResolvingAttempt(List sourceModel, Method mappingMethod, String mappedElement, - String dateFormat, String sourceReference, SelectionCriteria criteria) { + // CHECKSTYLE:OFF + private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedMethodHistory description, + FormattingParameters formattingParameters, SourceRHS sourceRHS, + SelectionCriteria criteria, + AnnotationMirror positionHint, + Supplier forger, + List builtIns, + FormattingMessager messager, boolean verboseLogging) { this.mappingMethod = mappingMethod; - this.mappedElement = mappedElement; - this.methods = filterPossibleCandidateMethods( sourceModel ); - this.dateFormat = dateFormat; - this.sourceReference = sourceReference; - this.virtualMethodCandidates = new HashSet(); + this.description = description; + this.formattingParameters = + formattingParameters == null ? FormattingParameters.EMPTY : formattingParameters; + this.sourceRHS = sourceRHS; + this.supportingMethodCandidates = new LinkedHashSet<>(); this.selectionCriteria = criteria; - this.savedPreferUpdateMapping = criteria.isPreferUpdateMapping(); + this.positionHint = positionHint; + this.forger = forger; + this.builtIns = builtIns; + this.messager = messager; + this.reportingLimitAmbiguous = verboseLogging ? Integer.MAX_VALUE : LIMIT_REPORTING_AMBIGUOUS; + this.methods = filterPossibleCandidateMethods( sourceModel, mappingMethod ); } + // CHECKSTYLE:ON - private List filterPossibleCandidateMethods(List candidateMethods) { - List result = new ArrayList( candidateMethods.size() ); + private List filterPossibleCandidateMethods(List candidateMethods, T mappingMethod) { + List result = new ArrayList<>( candidateMethods.size() ); for ( T candidate : candidateMethods ) { - if ( isCandidateForMapping( candidate ) ) { + if ( isCandidateForMapping( candidate ) && isNotSelfOrSelfAllowed( mappingMethod, candidate ) ) { result.add( candidate ); } } @@ -183,241 +221,258 @@ private List filterPossibleCandidateMethods(List candid return result; } + private boolean isNotSelfOrSelfAllowed(T mappingMethod, T candidate) { + return selectionCriteria == null || selectionCriteria.isSelfAllowed() || !candidate.equals( mappingMethod ); + } + private Assignment getTargetAssignment(Type sourceType, Type targetType) { + Assignment assignment; + // first simple mapping method - Assignment referencedMethod = resolveViaMethod( sourceType, targetType, false ); - if ( referencedMethod != null ) { - referencedMethod.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - return referencedMethod; + if ( allowMappingMethod() ) { + List> matches = getBestMatch( methods, sourceType, targetType ); + reportErrorWhenAmbiguous( matches, targetType ); + if ( !matches.isEmpty() ) { + assignment = toMethodRef( first( matches ) ); + assignment.setAssignment( sourceRHS ); + return assignment; + } } // then direct assignable - if ( sourceType.isAssignableTo( targetType ) || isPropertyMappable( sourceType, targetType ) ) { - Assignment simpleAssignment = AssignmentFactory.createDirect( sourceReference ); - return simpleAssignment; + if ( !hasQualfiers() ) { + if ( ( sourceType.isAssignableTo( targetType ) || + isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) + && allowDirect( sourceType, targetType ) ) { + Assignment simpleAssignment = sourceRHS; + return simpleAssignment; + } + } + // At this point the SourceType will either + // 1. be a String + // 2. or when its a primitive / wrapped type and analysis successful equal to its TargetType. But in that + // case it should have been direct assignable. + // In case of 1. and the target type is still a wrapped or primitive type we must assume that the check + // in NativeType is not successful. We don't want to go through type conversion, double mappings etc. + // with something that we already know to be wrong. + if ( sourceType.isLiteral() + && "java.lang.String".equals( sourceType.getFullyQualifiedName() ) + && targetType.isNative() ) { + return null; } // then type conversion - Assignment conversion = resolveViaConversion( sourceType, targetType ); - if ( conversion != null ) { - conversion.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - return conversion; - } + if ( allowConversion() ) { + if ( !hasQualfiers() ) { + ConversionAssignment conversion = resolveViaConversion( sourceType, targetType ); + if ( conversion != null ) { + conversion.reportMessageWhenNarrowing( messager, this ); + conversion.getAssignment().setAssignment( sourceRHS ); + return conversion.getAssignment(); + } + } - // check for a built-in method - Assignment builtInMethod = resolveViaBuiltInMethod( sourceType, targetType ); - if ( builtInMethod != null ) { - builtInMethod.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - usedVirtualMappings.addAll( virtualMethodCandidates ); - return builtInMethod; + // check for a built-in method + if ( !hasQualfiers() ) { + List> matches = getBestMatch( builtIns, sourceType, targetType ); + reportErrorWhenAmbiguous( matches, targetType ); + if ( !matches.isEmpty() ) { + assignment = toBuildInRef( first( matches ) ); + assignment.setAssignment( sourceRHS ); + usedSupportedMappings.addAll( supportingMethodCandidates ); + return assignment; + } + } } - // 2 step method, first: method(method(source)) - referencedMethod = resolveViaMethodAndMethod( sourceType, targetType ); - if ( referencedMethod != null ) { - usedVirtualMappings.addAll( virtualMethodCandidates ); - return referencedMethod; - } + if ( allow2Steps() ) { + // 2 step method, first: method(method(source)) + assignment = MethodMethod.getBestMatch( this, sourceType, targetType ); + if ( assignment != null ) { + usedSupportedMappings.addAll( supportingMethodCandidates ); + return assignment; + } - // 2 step method, then: method(conversion(source)) - referencedMethod = resolveViaConversionAndMethod( sourceType, targetType ); - if ( referencedMethod != null ) { - usedVirtualMappings.addAll( virtualMethodCandidates ); - return referencedMethod; - } + // 2 step method, then: method(conversion(source)) + if ( allowConversion() ) { + assignment = ConversionMethod.getBestMatch( this, sourceType, targetType ); + if ( assignment != null ) { + usedSupportedMappings.addAll( supportingMethodCandidates ); + return assignment; + } + } - // stop here when looking for update methods. - selectionCriteria.setPreferUpdateMapping( false ); + // stop here when looking for update methods. + selectionCriteria.setPreferUpdateMapping( false ); - // 2 step method, finally: conversion(method(source)) - conversion = resolveViaMethodAndConversion( sourceType, targetType ); - if ( conversion != null ) { - usedVirtualMappings.addAll( virtualMethodCandidates ); - return conversion; + // 2 step method, finally: conversion(method(source)) + if ( allowConversion() ) { + assignment = MethodConversion.getBestMatch( this, sourceType, targetType ); + if ( assignment != null ) { + usedSupportedMappings.addAll( supportingMethodCandidates ); + return assignment; + } + } + } + + if ( hasQualfiers() ) { + if ( (sourceType.isCollectionType() || sourceType.isArrayType()) && targetType.isIterableType() ) { + // Allow forging iterable mapping when no iterable mapping already found + return forger.get(); + } + else { + printQualifierMessage( selectionCriteria ); + } + } + else if ( allowMappingMethod() ) { + // only forge if we would allow mapping method + return forger.get(); } // if nothing works, alas, the result is null return null; } - private Assignment resolveViaConversion(Type sourceType, Type targetType) { - ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); + private boolean hasQualfiers() { + return selectionCriteria != null && selectionCriteria.hasQualfiers(); + } - if ( conversionProvider == null ) { - return null; + private void printQualifierMessage(SelectionCriteria selectionCriteria ) { + + List annotations = selectionCriteria.getQualifiers().stream() + .filter( DeclaredType.class::isInstance ) + .map( DeclaredType.class::cast ) + .map( DeclaredType::asElement ) + .map( Element::getSimpleName ) + .map( Name::toString ) + .map( a -> "@" + a ) + .collect( Collectors.toList() ); + List names = selectionCriteria.getQualifiedByNames(); + + if ( !annotations.isEmpty() && !names.isEmpty() ) { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_NO_QUALIFYING_METHOD_COMBINED, + Strings.join( names, MessageConstants.AND ), + Strings.join( annotations, MessageConstants.AND ) + ); + } + else if ( !annotations.isEmpty() ) { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_NO_QUALIFYING_METHOD_ANNOTATION, + Strings.join( annotations, MessageConstants.AND ) + ); + } + else { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_NO_QUALIFYING_METHOD_NAMED, + Strings.join( names, MessageConstants.AND ) + ); } - ConversionContext ctx = - new DefaultConversionContext( typeFactory, messager, sourceType, targetType, dateFormat ); - return conversionProvider.to( ctx ); } - /** - * Returns a reference to a method mapping the given source type to the given target type, if such a method - * exists. - */ - private Assignment resolveViaMethod(Type sourceType, Type targetType, boolean considerBuiltInMethods) { + private boolean allowDirect( Type sourceType, Type targetType ) { + if ( selectionCriteria != null && selectionCriteria.isAllowDirect() ) { + return true; + } - // first try to find a matching source method - SourceMethod matchingSourceMethod = getBestMatch( methods, sourceType, targetType ); + return allowDirect( sourceType ) || allowDirect( targetType ); + } - if ( matchingSourceMethod != null ) { - return getMappingMethodReference( matchingSourceMethod, targetType ); + private boolean allowDirect(Type type) { + if ( type.isPrimitive() ) { + return true; } - if ( considerBuiltInMethods ) { - return resolveViaBuiltInMethod( sourceType, targetType ); + if ( type.isEnumType() ) { + return true; } - return null; - } + if ( type.isArrayType() ) { + return type.isJavaLangType() || type.getComponentType().isPrimitive(); + } - private Assignment resolveViaBuiltInMethod(Type sourceType, Type targetType) { - BuiltInMethod matchingBuiltInMethod = - getBestMatch( builtInMethods.getBuiltInMethods(), sourceType, targetType ); + if ( type.isIterableOrStreamType() ) { + List typeParameters = type.getTypeParameters(); + // For iterable or stream direct mapping is enabled when: + // - The type is raw (no type parameters) + // - The type parameter is allowed + return typeParameters.isEmpty() || allowDirect( Collections.first( typeParameters ) ); + } - if ( matchingBuiltInMethod != null ) { - virtualMethodCandidates.add( new VirtualMappingMethod( matchingBuiltInMethod ) ); - ConversionContext ctx = new DefaultConversionContext( typeFactory, messager, - sourceType, - targetType, dateFormat ); - Assignment methodReference = AssignmentFactory.createMethodReference( matchingBuiltInMethod, ctx ); - methodReference.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - return methodReference; + if ( type.isMapType() ) { + List typeParameters = type.getTypeParameters(); + // For map type direct mapping is enabled when: + // - The type os raw (no type parameters + // - The key and value are direct assignable + return typeParameters.isEmpty() || + ( allowDirect( typeParameters.get( 0 ) ) && allowDirect( typeParameters.get( 1 ) ) ); } - return null; + return type.isJavaLangType(); } - /** - * Suppose mapping required from A to C and: - *
        - *
      • no direct referenced mapping method either built-in or referenced is available from A to C
      • - *
      • no conversion is available
      • - *
      • there is a method from A to B, methodX
      • - *
      • there is a method from B to C, methodY
      • - *
      - * then this method tries to resolve this combination and make a mapping methodY( methodX ( parameter ) ) - */ - private Assignment resolveViaMethodAndMethod(Type sourceType, Type targetType) { - - List methodYCandidates = new ArrayList( methods ); - methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); - - Assignment methodRefY = null; - - // Iterate over all source methods. Check if the return type matches with the parameter that we need. - // so assume we need a method from A to C we look for a methodX from A to B (all methods in the - // list form such a candidate). - // For each of the candidates, we need to look if there's a methodY, either - // sourceMethod or builtIn that fits the signature B to C. Only then there is a match. If we have a match - // a nested method call can be called. so C = methodY( methodX (A) ) - for ( Method methodYCandidate : methodYCandidates ) { - methodRefY = - resolveViaMethod( methodYCandidate.getSourceParameters().get( 0 ).getType(), targetType, true ); - - if ( methodRefY != null ) { - selectionCriteria.setPreferUpdateMapping( false ); - Assignment methodRefX = - resolveViaMethod( sourceType, methodYCandidate.getSourceParameters().get( 0 ).getType(), true ); - selectionCriteria.setPreferUpdateMapping( savedPreferUpdateMapping ); - if ( methodRefX != null ) { - methodRefY.setAssignment( methodRefX ); - methodRefX.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - break; - } - else { - // both should match; - virtualMethodCandidates.clear(); - methodRefY = null; - } - } - } - return methodRefY; + private boolean allowConversion() { + return selectionCriteria != null && selectionCriteria.isAllowConversion(); } - /** - * Suppose mapping required from A to C and: - *
        - *
      • there is a conversion from A to B, conversionX
      • - *
      • there is a method from B to C, methodY
      • - *
      - * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) - */ - private Assignment resolveViaConversionAndMethod(Type sourceType, Type targetType) { - - List methodYCandidates = new ArrayList( methods ); - methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); + private boolean allowMappingMethod() { + return selectionCriteria != null && selectionCriteria.isAllowMappingMethod(); + } - Assignment methodRefY = null; + private boolean allow2Steps() { + return selectionCriteria != null && selectionCriteria.isAllow2Steps(); + } - for ( Method methodYCandidate : methodYCandidates ) { - methodRefY = - resolveViaMethod( methodYCandidate.getSourceParameters().get( 0 ).getType(), targetType, true ); + private ConversionAssignment resolveViaConversion(Type sourceType, Type targetType) { - if ( methodRefY != null ) { - Assignment conversionXRef = - resolveViaConversion( sourceType, methodYCandidate.getSourceParameters().get( 0 ).getType() ); - if ( conversionXRef != null ) { - methodRefY.setAssignment( conversionXRef ); - conversionXRef.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - break; - } - else { - // both should match - virtualMethodCandidates.clear(); - methodRefY = null; - } - } + ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); + if ( conversionProvider == null ) { + return null; } - return methodRefY; - } - - /** - * Suppose mapping required from A to C and: - *
        - *
      • there is a conversion from A to B, conversionX
      • - *
      • there is a method from B to C, methodY
      • - *
      - * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) - */ - private Assignment resolveViaMethodAndConversion(Type sourceType, Type targetType) { + ConversionContext ctx = new DefaultConversionContext( + typeFactory, + messager, + sourceType, + targetType, + formattingParameters + ); - List methodXCandidates = new ArrayList( methods ); - methodXCandidates.addAll( builtInMethods.getBuiltInMethods() ); + // add helper methods required in conversion + Set allUsedFields = new HashSet<>( mapperReferences ); + SupportingField.addAllFieldsIn( supportingMethodCandidates, allUsedFields ); - Assignment conversionYRef = null; + for ( FieldReference helperField : conversionProvider.getRequiredHelperFields( ctx ) ) { + Field field = SupportingField.getSafeField( null, helperField, allUsedFields ); + allUsedFields.add( field ); + usedSupportedFields.add( field ); + } - // search the other way around - for ( Method methodXCandidate : methodXCandidates ) { - if ( methodXCandidate.getMappingTargetParameter() != null ) { - continue; - } + for ( HelperMethod helperMethod : conversionProvider.getRequiredHelperMethods( ctx ) ) { + SupportingMappingMethod supportingMappingMethod = + new SupportingMappingMethod( helperMethod ); + SupportingField.addAllFieldsIn( Collections.asSet( supportingMappingMethod ), allUsedFields ); + usedSupportedMappings.add( supportingMappingMethod ); + } - Assignment methodRefX = resolveViaMethod( - sourceType, - methodXCandidate.getReturnType(), - true - ); - if ( methodRefX != null ) { - conversionYRef = resolveViaConversion( methodXCandidate.getReturnType(), targetType ); - if ( conversionYRef != null ) { - conversionYRef.setAssignment( methodRefX ); - methodRefX.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - break; - } - else { - // both should match; - virtualMethodCandidates.clear(); - conversionYRef = null; - } - } + Assignment conversion = conversionProvider.to( ctx ); + if ( conversion != null ) { + return new ConversionAssignment( sourceType, targetType, conversion ); } - return conversionYRef; + return null; } private boolean isCandidateForMapping(Method methodCandidate) { + if ( methodCandidate.getConditionOptions().isAnyStrategyApplicable() ) { + return false; + } return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate ); } @@ -436,103 +491,96 @@ private boolean isUpdateMethodForMapping(Method methodCandidate) { && !methodCandidate.isLifecycleCallbackMethod(); } - private T getBestMatch(List methods, Type sourceType, Type returnType) { - - List candidates = methodSelectors.getMatchingMethods( - mappingMethod, + private List> getBestMatch(List methods, Type source, Type target) { + return methodSelectors.getMatchingMethods( methods, - sourceType, - returnType, - selectionCriteria + SelectionContext.forMappingMethods( mappingMethod, source, target, selectionCriteria, typeFactory ) ); + } + + private void reportErrorWhenAmbiguous(List> candidates, Type target) { // raise an error if more than one mapping method is suitable to map the given source type // into the target type if ( candidates.size() > 1 ) { - if ( mappedElement != null ) { - messager.printMessage( mappingMethod.getExecutable(), - Message.GENERAL_AMBIGIOUS_MAPPING_METHOD, - mappedElement, - returnType, - Strings.join( candidates, ", " ) + String descriptionStr = ""; + if ( description != null ) { + descriptionStr = description.createSourcePropertyErrorMessage(); + } + else { + descriptionStr = sourceRHS.getSourceErrorMessagePart(); + } + + if ( sourceRHS.getSourceErrorMessagePart() != null ) { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_AMBIGUOUS_MAPPING_METHOD, + descriptionStr, + target.describe(), + join( candidates ) ); } else { - messager.printMessage( mappingMethod.getExecutable(), - Message.GENERAL_AMBIGIOUS_FACTORY_METHOD, - returnType, - Strings.join( candidates, ", " ) + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_AMBIGUOUS_FACTORY_METHOD, + target.describe(), + join( candidates ) ); } } - - if ( !candidates.isEmpty() ) { - return candidates.get( 0 ); - } - - return null; } - private Assignment getMappingMethodReference(SourceMethod method, - Type targetType) { - MapperReference mapperReference = findMapperReference( method ); + private Assignment toMethodRef(SelectedMethod selectedMethod) { + MapperReference mapperReference = findMapperReference( selectedMethod.getMethod() ); - return AssignmentFactory.createMethodReference( - method, + return MethodReference.forMapperReference( + selectedMethod.getMethod(), mapperReference, - SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null + selectedMethod.getParameterBindings() ); } - private MapperReference findMapperReference(SourceMethod method) { - for ( MapperReference ref : mapperReferences ) { - if ( ref.getType().equals( method.getDeclaringMapper() ) ) { - ref.setUsed( !method.isStatic() ); - ref.setTypeRequiresImport( true ); - return ref; - } - } - return null; + private Assignment toBuildInRef(SelectedMethod selectedMethod) { + BuiltInMethod method = selectedMethod.getMethod(); + Set allUsedFields = new HashSet<>( mapperReferences ); + SupportingField.addAllFieldsIn( supportingMethodCandidates, allUsedFields ); + SupportingMappingMethod supportingMappingMethod = new SupportingMappingMethod( method, allUsedFields ); + supportingMethodCandidates.add( supportingMappingMethod ); + ConversionContext ctx = new DefaultConversionContext( + typeFactory, + messager, + method.getMappingSourceType(), + method.getResultType(), + formattingParameters + ); + Assignment methodReference = MethodReference.forBuiltInMethod( method, ctx ); + methodReference.setAssignment( sourceRHS ); + return methodReference; } /** - * Whether the specified property can be mapped from source to target or not. A mapping if possible if one of - * the following conditions is true: - *
        - *
      • the source type is assignable to the target type
      • - *
      • a mapping method exists
      • - *
      • a built-in conversion exists
      • - *
      • the property is of a collection or map type and the constructor of the target type (either itself or its - * implementation type) accepts the source type.
      • - *
      - * - * @return {@code true} if the specified property can be mapped, {@code false} otherwise. + * Whether the given source and target type are both a collection type or both a map type and the source value + * can be propagated via a copy constructor. */ - private boolean isPropertyMappable(Type sourceType, Type targetType) { - boolean collectionOrMapTargetTypeHasCompatibleConstructor = false; + private boolean isAssignableThroughCollectionCopyConstructor(Type sourceType, Type targetType) { + boolean bothCollectionOrMap = false; - if ( sourceType.isCollectionType() && targetType.isCollectionType() ) { - collectionOrMapTargetTypeHasCompatibleConstructor = collectionTypeHasCompatibleConstructor( - sourceType, - targetType.getImplementationType() != null - ? targetType.getImplementationType() : targetType - ); + if ( ( sourceType.isCollectionType() && targetType.isCollectionType() ) || + ( sourceType.isMapType() && targetType.isMapType() ) ) { + bothCollectionOrMap = true; } - if ( sourceType.isMapType() && targetType.isMapType() ) { - collectionOrMapTargetTypeHasCompatibleConstructor = mapTypeHasCompatibleConstructor( + if ( bothCollectionOrMap ) { + return hasCompatibleCopyConstructor( sourceType, - targetType.getImplementationType() != null - ? targetType.getImplementationType() : targetType + targetType.getImplementationType() != null ? targetType.getImplementationType() : targetType ); } - if ( ( ( targetType.isCollectionType() || targetType.isMapType() ) - && collectionOrMapTargetTypeHasCompatibleConstructor ) ) { - return true; - } - return false; } @@ -545,63 +593,553 @@ private boolean isPropertyMappable(Type sourceType, Type targetType) { * @return {@code true} if the target type has a constructor accepting the given source type, {@code false} * otherwise. */ - private boolean collectionTypeHasCompatibleConstructor(Type sourceType, Type targetType) { - // note (issue #127): actually this should check for the presence of a matching constructor, with help of - // Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead - // we just check whether the target type is parameterized in a way that it implicitly should have a - // constructor which accepts the source type + private boolean hasCompatibleCopyConstructor(Type sourceType, Type targetType) { + if ( targetType.isPrimitive() ) { + return false; + } + + List targetTypeConstructors = ElementFilter.constructorsIn( + targetType.getTypeElement().getEnclosedElements() ); + + for ( ExecutableElement constructor : targetTypeConstructors ) { + if ( constructor.getParameters().size() != 1 ) { + continue; + } + + // get the constructor resolved against the type arguments of specific target type + ExecutableType typedConstructor = (ExecutableType) typeUtils.asMemberOf( + (DeclaredType) targetType.getTypeMirror(), + constructor + ); + + TypeMirror parameterType = Collections.first( typedConstructor.getParameterTypes() ); + if ( parameterType.getKind() == TypeKind.DECLARED ) { + // replace any possible type bounds in the type parameters of the parameter types, as in JDK super + // type bounds in the arguments are returned from asMemberOf with "? extends ? super XX" + // + // It might also be enough to just remove "? super" from type parameters of + // targetType.getTypeMirror() in case we're in JDK. And that would be something that should be + // handled in SpecificCompilerWorkarounds... - TypeMirror sourceElementType = sourceType.getTypeParameters().isEmpty() - ? typeFactory.getType( Object.class ).getTypeMirror() - : sourceType.getTypeParameters().get( 0 ).getTypeMirror(); + DeclaredType p = (DeclaredType) parameterType; + List typeArguments = new ArrayList<>( p.getTypeArguments().size() ); - TypeMirror targetElementType = targetType.getTypeParameters().isEmpty() - ? typeFactory.getType( Object.class ).getTypeMirror() - : targetType.getTypeParameters().get( 0 ).getTypeMirror(); + for ( TypeMirror tArg : p.getTypeArguments() ) { + typeArguments.add( typeFactory.getTypeBound( tArg ) ); + } + parameterType = typeUtils.getDeclaredType( + (TypeElement) p.asElement(), + typeArguments.toArray( new TypeMirror[typeArguments.size()] ) + ); + } + + if ( typeUtils.isAssignable( sourceType.getTypeMirror(), parameterType ) ) { + return true; + } + } - return SpecificCompilerWorkarounds.isAssignable( typeUtils, sourceElementType, targetElementType ); + return false; } - /** - * Whether the given target type has a single-argument constructor which accepts the given source type. - * - * @param sourceType the source type - * @param targetType the target type - * - * @return {@code true} if the target type has a constructor accepting the given source type, {@code false} - * otherwise. - */ - private boolean mapTypeHasCompatibleConstructor(Type sourceType, Type targetType) { - // note (issue #127): actually this should check for the presence of a matching constructor, with help of - // Types#asMemberOf(); but this method seems to not work correctly in the Eclipse implementation, so instead - // we just check whether the target type is parameterized in a way that it implicitly should have a - // constructor which accepts the source type + private String join( List> candidates ) { - TypeMirror sourceKeyType; - TypeMirror targetKeyType; - TypeMirror sourceValueType; - TypeMirror targetValueType; + String candidateStr = candidates.stream() + .limit( reportingLimitAmbiguous ) + .map( m -> m.getMethod().describe() ) + .collect( Collectors.joining( ", " ) ); - if ( sourceType.getTypeParameters().isEmpty() ) { - sourceKeyType = typeFactory.getType( Object.class ).getTypeMirror(); - sourceValueType = typeFactory.getType( Object.class ).getTypeMirror(); + if ( candidates.size() > reportingLimitAmbiguous ) { + candidateStr += String.format( "... and %s more", candidates.size() - reportingLimitAmbiguous ); } - else { - sourceKeyType = sourceType.getTypeParameters().get( 0 ).getTypeMirror(); - sourceValueType = sourceType.getTypeParameters().get( 1 ).getTypeMirror(); + return candidateStr; + } + } + + private static class ConversionAssignment { + + private final Type sourceType; + private final Type targetType; + private final Assignment assignment; + + ConversionAssignment(Type sourceType, Type targetType, Assignment assignment) { + this.sourceType = sourceType; + this.targetType = targetType; + this.assignment = assignment; + } + + Assignment getAssignment() { + return assignment; + } + + void reportMessageWhenNarrowing(FormattingMessager messager, ResolvingAttempt attempt) { + + Type source = sourceType; + if ( sourceType.isOptionalType() ) { + source = sourceType.getOptionalBaseType(); } - if ( targetType.getTypeParameters().isEmpty() ) { - targetKeyType = typeFactory.getType( Object.class ).getTypeMirror(); - targetValueType = typeFactory.getType( Object.class ).getTypeMirror(); + Type target = targetType; + if ( targetType.isOptionalType() ) { + target = targetType.getOptionalBaseType(); } - else { - targetKeyType = targetType.getTypeParameters().get( 0 ).getTypeMirror(); - targetValueType = targetType.getTypeParameters().get( 1 ).getTypeMirror(); + if ( NativeTypes.isNarrowing( source.getFullyQualifiedName(), target.getFullyQualifiedName() ) ) { + ReportingPolicyGem policy = attempt.mappingMethod.getOptions().getMapper().typeConversionPolicy(); + if ( policy == ReportingPolicyGem.WARN ) { + report( messager, attempt, Message.CONVERSION_LOSSY_WARNING ); + } + else if ( policy == ReportingPolicyGem.ERROR ) { + report( messager, attempt, Message.CONVERSION_LOSSY_ERROR ); + } } + } + + private void report(FormattingMessager messager, ResolvingAttempt attempt, Message message) { + + messager.printMessage( + attempt.mappingMethod.getExecutable(), + attempt.positionHint, + message, + attempt.sourceRHS.getSourceErrorMessagePart(), + sourceType.describe(), + targetType.describe() + ); + } - return typeUtils.isAssignable( sourceKeyType, targetKeyType ) - && typeUtils.isAssignable( sourceValueType, targetValueType ); + String shortName() { + return sourceType.getName() + "-->" + targetType.getName(); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ConversionAssignment that = (ConversionAssignment) o; + return sourceType.equals( that.sourceType ) && targetType.equals( that.targetType ); + } + + @Override + public int hashCode() { + return Objects.hash( sourceType, targetType ); + } + } + + private enum BestMatchType { + IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES, + IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES, } + + /** + * Suppose mapping required from A to C and: + *
        + *
      • no direct referenced mapping method either built-in or referenced is available from A to C
      • + *
      • no conversion is available
      • + *
      • there is a method from A to B, methodX
      • + *
      • there is a method from B to C, methodY
      • + *
      + * then this method tries to resolve this combination and make a mapping methodY( methodX ( parameter ) ) + * + * NOTE method X cannot be an update method + */ + private static class MethodMethod { + + private final ResolvingAttempt attempt; + private final List xMethods; + private final List yMethods; + private final Function, Assignment> xCreate; + private final Function, Assignment> yCreate; + + // results + private boolean hasResult = false; + private Assignment result = null; + + static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targetType) { + MethodMethod mmAttempt = + new MethodMethod<>( att, att.methods, att.methods, att::toMethodRef, att::toMethodRef ) + .getBestMatch( sourceType, targetType ); + if ( mmAttempt.hasResult ) { + return mmAttempt.result; + } + if ( att.hasQualfiers() ) { + mmAttempt = mmAttempt.getBestMatchIgnoringQualifiersBeforeY( sourceType, targetType ); + if ( mmAttempt.hasResult ) { + return mmAttempt.result; + } + + mmAttempt = mmAttempt.getBestMatchIgnoringQualifiersAfterY( sourceType, targetType ); + if ( mmAttempt.hasResult ) { + return mmAttempt.result; + } + } + if ( att.allowConversion() ) { + MethodMethod mbAttempt = + new MethodMethod<>( att, att.methods, att.builtIns, att::toMethodRef, att::toBuildInRef ) + .getBestMatch( sourceType, targetType ); + if ( mbAttempt.hasResult ) { + return mbAttempt.result; + } + MethodMethod bmAttempt = + new MethodMethod<>( att, att.builtIns, att.methods, att::toBuildInRef, att::toMethodRef ) + .getBestMatch( sourceType, targetType ); + if ( bmAttempt.hasResult ) { + return bmAttempt.result; + } + MethodMethod bbAttempt = + new MethodMethod<>( att, att.builtIns, att.builtIns, att::toBuildInRef, att::toBuildInRef ) + .getBestMatch( sourceType, targetType ); + return bbAttempt.result; + } + + return null; + } + + MethodMethod(ResolvingAttempt attempt, List xMethods, List yMethods, + Function, Assignment> xCreate, + Function, Assignment> yCreate) { + this.attempt = attempt; + this.xMethods = xMethods; + this.yMethods = yMethods; + this.xCreate = xCreate; + this.yCreate = yCreate; + } + + private MethodMethod getBestMatch(Type sourceType, Type targetType) { + return getBestMatch( sourceType, targetType, null ); + } + + private MethodMethod getBestMatchIgnoringQualifiersBeforeY(Type sourceType, Type targetType) { + return getBestMatch( sourceType, targetType, BestMatchType.IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES ); + } + + private MethodMethod getBestMatchIgnoringQualifiersAfterY(Type sourceType, Type targetType) { + return getBestMatch( sourceType, targetType, BestMatchType.IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES ); + } + + private MethodMethod getBestMatch(Type sourceType, Type targetType, BestMatchType matchType) { + + Set yCandidates = new HashSet<>(); + Map, List>> xCandidates = new LinkedHashMap<>(); + Map, Type> typesInTheMiddle = new LinkedHashMap<>(); + + // Iterate over all source methods. Check if the return type matches with the parameter that we need. + // so assume we need a method from A to C we look for a methodX from A to B (all methods in the + // list form such a candidate). + // For each of the candidates, we need to look if there's a methodY, either + // sourceMethod or builtIn that fits the signature B to C. Only then there is a match. If we have a match + // a nested method call can be called. so C = methodY( methodX (A) ) + attempt.selectionCriteria.setPreferUpdateMapping( false ); + attempt.selectionCriteria.setIgnoreQualifiers( + matchType == BestMatchType.IGNORE_QUALIFIERS_BEFORE_Y_CANDIDATES ); + + for ( T2 yCandidate : yMethods ) { + Type ySourceType = yCandidate.getMappingSourceType(); + ySourceType = ySourceType.resolveGenericTypeParameters( targetType, yCandidate.getResultType() ); + Type yTargetType = yCandidate.getResultType(); + if ( ySourceType == null + || !yTargetType.isRawAssignableTo( targetType ) + || JL_OBJECT_NAME.equals( ySourceType.getFullyQualifiedName() ) ) { + // java.lang.Object as intermediate result + continue; + } + List> xMatches = attempt.getBestMatch( xMethods, sourceType, ySourceType ); + if ( !xMatches.isEmpty() ) { + for ( SelectedMethod x : xMatches ) { + xCandidates.put( x, new ArrayList<>() ); + typesInTheMiddle.put( x, ySourceType ); + } + yCandidates.add( yCandidate ); + } + } + attempt.selectionCriteria.setPreferUpdateMapping( true ); + attempt.selectionCriteria.setIgnoreQualifiers( + matchType == BestMatchType.IGNORE_QUALIFIERS_AFTER_Y_CANDIDATES ); + + // collect all results + List yCandidatesList = new ArrayList<>( yCandidates ); + Iterator, List>>> i = xCandidates.entrySet().iterator(); + while ( i.hasNext() ) { + Map.Entry, List>> entry = i.next(); + Type typeInTheMiddle = typesInTheMiddle.get( entry.getKey() ); + entry.getValue().addAll( attempt.getBestMatch( yCandidatesList, typeInTheMiddle, targetType ) ); + if ( entry.getValue().isEmpty() ) { + i.remove(); + } + } + + attempt.selectionCriteria.setIgnoreQualifiers( false ); + // no results left + if ( xCandidates.isEmpty() ) { + return this; + } + hasResult = true; + + // get result, there should be one entry left with only one value + if ( xCandidates.size() == 1 && firstValue( xCandidates ).size() == 1 ) { + Assignment methodRefY = yCreate.apply( first( firstValue( xCandidates ) ) ); + Assignment methodRefX = xCreate.apply( firstKey( xCandidates ) ); + methodRefY.setAssignment( methodRefX ); + methodRefX.setAssignment( attempt.sourceRHS ); + result = methodRefY; + } + else { + reportAmbiguousError( xCandidates, targetType ); + } + return this; + + } + + void reportAmbiguousError(Map, List>> xCandidates, Type target) { + StringBuilder result = new StringBuilder(); + xCandidates.entrySet() + .stream() + .limit( attempt.reportingLimitAmbiguous ) + .forEach( e -> result.append( "method(s)Y: " ) + .append( attempt.join( e.getValue() ) ) + .append( ", methodX: " ) + .append( e.getKey().getMethod().describe() ) + .append( "; " ) ); + attempt.messager.printMessage( + attempt.mappingMethod.getExecutable(), + attempt.positionHint, + Message.GENERAL_AMBIGUOUS_MAPPING_METHODY_METHODX, + attempt.sourceRHS.getSourceType().getName() + " " + attempt.sourceRHS.getSourceParameterName(), + target.getName(), + result.toString() ); + } + } + + /** + * Suppose mapping required from A to C and: + *
        + *
      • there is a conversion from A to B, conversionX
      • + *
      • there is a method from B to C, methodY
      • + *
      + * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) + * + * Instead of directly using a built in method candidate, all the return types as 'B' of all available built-in + * methods are used to resolve a mapping (assignment) from result type to 'B'. If a match is found, an attempt + * is done to find a matching type conversion. + */ + private static class ConversionMethod { + + private final ResolvingAttempt attempt; + private final List methods; + private final Function, Assignment> create; + + // results + private boolean hasResult = false; + private Assignment result = null; + + static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targetType) { + ConversionMethod mAttempt = new ConversionMethod<>( att, att.methods, att::toMethodRef ) + .getBestMatch( sourceType, targetType ); + if ( mAttempt.hasResult ) { + return mAttempt.result; + } + ConversionMethod bAttempt = + new ConversionMethod<>( att, att.builtIns, att::toBuildInRef ) + .getBestMatch( sourceType, targetType ); + return bAttempt.result; + } + + ConversionMethod(ResolvingAttempt attempt, List methods, Function, Assignment> create) { + this.attempt = attempt; + this.methods = methods; + this.create = create; + } + + private ConversionMethod getBestMatch(Type sourceType, Type targetType) { + + List yCandidates = new ArrayList<>(); + Map>> xRefCandidates = new LinkedHashMap<>(); + + for ( T yCandidate : methods ) { + Type ySourceType = yCandidate.getMappingSourceType(); + ySourceType = ySourceType.resolveParameterToType( targetType, yCandidate.getResultType() ).getMatch(); + Type yTargetType = yCandidate.getResultType(); + if ( ySourceType == null + || !yTargetType.isRawAssignableTo( targetType ) + || JL_OBJECT_NAME.equals( ySourceType.getFullyQualifiedName() ) ) { + // java.lang.Object as intermediate result + continue; + } + ConversionAssignment xRefCandidate = attempt.resolveViaConversion( sourceType, ySourceType ); + if ( xRefCandidate != null ) { + xRefCandidates.put( xRefCandidate, new ArrayList<>() ); + yCandidates.add( yCandidate ); + } + } + + // collect all results + Iterator>>> i = xRefCandidates.entrySet().iterator(); + while ( i.hasNext() ) { + Map.Entry>> entry = i.next(); + entry.getValue().addAll( attempt.getBestMatch( yCandidates, entry.getKey().targetType, targetType ) ); + if ( entry.getValue().isEmpty() ) { + i.remove(); + } + } + + // no results left + if ( xRefCandidates.isEmpty() ) { + return this; + } + hasResult = true; + + // get result, there should be one entry left with only one value + if ( xRefCandidates.size() == 1 && firstValue( xRefCandidates ).size() == 1 ) { + Assignment methodRefY = create.apply( first( firstValue( xRefCandidates ) ) ); + ConversionAssignment conversionRefX = firstKey( xRefCandidates ); + conversionRefX.reportMessageWhenNarrowing( attempt.messager, attempt ); + methodRefY.setAssignment( conversionRefX.assignment ); + conversionRefX.assignment.setAssignment( attempt.sourceRHS ); + result = methodRefY; + } + else { + reportAmbiguousError( xRefCandidates, targetType ); + } + return this; + + } + + void reportAmbiguousError(Map>> xRefCandidates, Type target) { + StringBuilder result = new StringBuilder(); + xRefCandidates.entrySet() + .stream() + .limit( attempt.reportingLimitAmbiguous ) + .forEach( e -> result.append( "method(s)Y: " ) + .append( attempt.join( e.getValue() ) ) + .append( ", conversionX: " ) + .append( e.getKey().shortName() ) + .append( "; " ) ); + attempt.messager.printMessage( + attempt.mappingMethod.getExecutable(), + attempt.positionHint, + Message.GENERAL_AMBIGUOUS_MAPPING_METHODY_CONVERSIONX, + attempt.sourceRHS.getSourceType().getName() + " " + attempt.sourceRHS.getSourceParameterName(), + target.getName(), + result.toString() ); + } + } + + /** + * Suppose mapping required from A to C and: + *
        + *
      • there is a method from A to B, methodX
      • + *
      • there is a conversion from B to C, conversionY
      • + *
      + * then this method tries to resolve this combination and make a mapping conversionY( methodX ( parameter ) ) + * + * Instead of directly using a built in method candidate, all the return types as 'B' of all available built-in + * methods are used to resolve a mapping (assignment) from source type to 'B'. If a match is found, an attempt + * is done to find a matching type conversion. + * + * NOTE methodX cannot be an update method + */ + private static class MethodConversion { + + private final ResolvingAttempt attempt; + private final List methods; + private final Function, Assignment> create; + + // results + private boolean hasResult = false; + private Assignment result = null; + + static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targetType) { + MethodConversion mAttempt = new MethodConversion<>( att, att.methods, att::toMethodRef ) + .getBestMatch( sourceType, targetType ); + if ( mAttempt.hasResult ) { + return mAttempt.result; + } + MethodConversion bAttempt = new MethodConversion<>( att, att.builtIns, att::toBuildInRef ) + .getBestMatch( sourceType, targetType ); + return bAttempt.result; + } + + MethodConversion(ResolvingAttempt attempt, List methods, Function, Assignment> create) { + this.attempt = attempt; + this.methods = methods; + this.create = create; + } + + private MethodConversion getBestMatch(Type sourceType, Type targetType) { + + List xCandidates = new ArrayList<>(); + Map>> yRefCandidates = new LinkedHashMap<>(); + + // search through methods, and select egible candidates + for ( T xCandidate : methods ) { + Type xTargetType = xCandidate.getReturnType(); + Type xSourceType = xCandidate.getMappingSourceType(); + xTargetType = xTargetType.resolveParameterToType( sourceType, xSourceType ).getMatch(); + if ( xTargetType == null + || xCandidate.isUpdateMethod() + || !sourceType.isRawAssignableTo( xSourceType ) + || JL_OBJECT_NAME.equals( xTargetType.getFullyQualifiedName() ) ) { + // skip update methods || java.lang.Object as intermediate result + continue; + } + ConversionAssignment yRefCandidate = attempt.resolveViaConversion( xTargetType, targetType ); + if ( yRefCandidate != null ) { + yRefCandidates.put( yRefCandidate, new ArrayList<>() ); + xCandidates.add( xCandidate ); + } + } + + // collect all results + Iterator>>> i = yRefCandidates.entrySet().iterator(); + while ( i.hasNext() ) { + Map.Entry>> entry = i.next(); + entry.getValue().addAll( attempt.getBestMatch( xCandidates, sourceType, entry.getKey().sourceType ) ); + if ( entry.getValue().isEmpty() ) { + i.remove(); + } + } + + // no results left + if ( yRefCandidates.isEmpty() ) { + return this; + } + hasResult = true; + + // get result, there should be one entry left with only one value + if ( yRefCandidates.size() == 1 && firstValue( yRefCandidates ).size() == 1 ) { + Assignment methodRefX = create.apply( first( firstValue( yRefCandidates ) ) ); + ConversionAssignment conversionRefY = firstKey( yRefCandidates ); + conversionRefY.reportMessageWhenNarrowing( attempt.messager, attempt ); + methodRefX.setAssignment( attempt.sourceRHS ); + conversionRefY.assignment.setAssignment( methodRefX ); + result = conversionRefY.assignment; + } + else { + reportAmbiguousError( yRefCandidates, targetType ); + } + return this; + + } + + void reportAmbiguousError(Map>> yRefCandidates, Type target) { + StringBuilder result = new StringBuilder(); + yRefCandidates.entrySet() + .stream() + .limit( attempt.reportingLimitAmbiguous ) + .forEach( e -> result.append( "conversionY: " ) + .append( e.getKey().shortName() ) + .append( ", method(s)X: " ) + .append( attempt.join( e.getValue() ) ) + .append( "; " ) ); + attempt.messager.printMessage( + attempt.mappingMethod.getExecutable(), + attempt.positionHint, + Message.GENERAL_AMBIGUOUS_MAPPING_CONVERSIONY_METHODX, + attempt.sourceRHS.getSourceType().getName() + " " + attempt.sourceRHS.getSourceParameterName(), + target.getName(), + result.toString() ); + } + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/package-info.java index 9f2a116d82..d8ab59ad5a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/package-info.java index 1933647df3..b0aa0d0a16 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/services/Services.java b/processor/src/main/java/org/mapstruct/ap/internal/services/Services.java deleted file mode 100644 index dcbdb50239..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/services/Services.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.services; - -import java.util.ServiceLoader; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * A simple locator for SPI implementations. - * - * @author Christian Schuster - */ -public class Services { - - private static final ConcurrentMap, Object> SERVICES = new ConcurrentHashMap, Object>(); - - private Services() { - } - - public static T get(Class serviceType, T defaultValue) { - @SuppressWarnings("unchecked") - T service = (T) SERVICES.get( serviceType ); - - if ( service == null ) { - service = loadAndCache( serviceType, defaultValue ); - } - - return service; - } - - private static T loadAndCache(Class serviceType, T defaultValue) { - T service = find( serviceType ); - if ( service == null ) { - service = defaultValue; - } - - @SuppressWarnings("unchecked") - T cached = (T) SERVICES.putIfAbsent( serviceType, service ); - if ( cached != null ) { - service = (T) cached; - } - - return service; - } - - private static T find(Class spi) { - T matchingImplementation = null; - - for ( T implementation : ServiceLoader.load( spi, spi.getClassLoader() ) ) { - if ( matchingImplementation == null ) { - matchingImplementation = implementation; - } - else { - throw new IllegalStateException( - "Multiple implementations have been found for the service provider interface " - + spi.getCanonicalName() + ": " + matchingImplementation.getClass().getCanonicalName() + ", " - + implementation.getClass().getCanonicalName() + "." - ); - } - } - - return matchingImplementation; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java new file mode 100644 index 0000000000..67ce6e10ff --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractElementUtilsDecorator.java @@ -0,0 +1,331 @@ +/* + * 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.util; + +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; + +import org.mapstruct.ap.spi.TypeHierarchyErroneousException; + +import static javax.lang.model.util.ElementFilter.fieldsIn; +import static javax.lang.model.util.ElementFilter.methodsIn; + +/** + * MapStruct specific abstract implementation of {@link ElementUtils}. + * This allows us to provide different implementations for different compilers and / or + * to allow us for easier implementation of using the module system. + */ +public abstract class AbstractElementUtilsDecorator implements ElementUtils { + + private final Elements delegate; + /** + * The module element when running with the module system, + * {@code null} otherwise. + */ + private final Element moduleElement; + + @IgnoreJRERequirement + AbstractElementUtilsDecorator(ProcessingEnvironment processingEnv, TypeElement mapperElement) { + this.delegate = processingEnv.getElementUtils(); + if ( SourceVersion.RELEASE_8.compareTo( processingEnv.getSourceVersion() ) >= 0 ) { + // We are running with Java 8 or lower + this.moduleElement = null; + } + else { + this.moduleElement = this.delegate.getModuleOf( mapperElement ); + } + } + + @Override + @IgnoreJRERequirement + public PackageElement getPackageElement(CharSequence name) { + if ( this.moduleElement == null ) { + return this.delegate.getPackageElement( name ); + } + + // If the module element is not null then we must be running on Java 8+ + return this.delegate.getPackageElement( (ModuleElement) moduleElement, name ); + } + + @Override + @IgnoreJRERequirement + public TypeElement getTypeElement(CharSequence name) { + if ( this.moduleElement == null ) { + return this.delegate.getTypeElement( name ); + } + + // If the module element is not null then we must be running on Java 8+ + return this.delegate.getTypeElement( (ModuleElement) moduleElement, name ); + } + + @Override + public Map getElementValuesWithDefaults( + AnnotationMirror a) { + return delegate.getElementValuesWithDefaults( a ); + } + + @Override + public String getDocComment(Element e) { + return delegate.getDocComment( e ); + } + + @Override + public boolean isDeprecated(Element e) { + return delegate.isDeprecated( e ); + } + + @Override + public Name getBinaryName(TypeElement type) { + return delegate.getBinaryName( type ); + } + + @Override + public PackageElement getPackageOf(Element type) { + return delegate.getPackageOf( type ); + } + + @Override + public List getAllMembers(TypeElement type) { + return delegate.getAllMembers( type ); + } + + @Override + public List getAllAnnotationMirrors(Element e) { + return delegate.getAllAnnotationMirrors( e ); + } + + @Override + public boolean hides(Element hider, Element hidden) { + return delegate.hides( hider, hidden ); + } + + @Override + public boolean overrides(ExecutableElement overrider, ExecutableElement overridden, TypeElement type) { + return delegate.overrides( overrider, overridden, type ); + } + + @Override + public String getConstantExpression(Object value) { + return delegate.getConstantExpression( value ); + } + + @Override + public void printElements(Writer w, Element... elements) { + delegate.printElements( w, elements ); + } + + @Override + public Name getName(CharSequence cs) { + return delegate.getName( cs ); + } + + @Override + public boolean isFunctionalInterface(TypeElement type) { + return delegate.isFunctionalInterface( type ); + } + + @Override + public List getAllEnclosedExecutableElements(TypeElement element) { + List enclosedElements = new ArrayList<>(); + element = replaceTypeElementIfNecessary( element ); + addEnclosedMethodsInHierarchy( enclosedElements, new HashSet<>(), element, element ); + + return enclosedElements; + } + + @Override + public List getAllEnclosedFields( TypeElement element) { + List enclosedElements = new ArrayList<>(); + element = replaceTypeElementIfNecessary( element ); + addEnclosedFieldsInHierarchy( enclosedElements, element, element ); + + return enclosedElements; + } + + private void addEnclosedMethodsInHierarchy(List alreadyAdded, + Collection alreadyVisitedElements, + TypeElement element, + TypeElement parentType) { + if ( element != parentType ) { // otherwise the element was already checked for replacement + element = replaceTypeElementIfNecessary( element ); + } + + if ( element.asType().getKind() == TypeKind.ERROR ) { + throw new TypeHierarchyErroneousException( element ); + } + + if ( !alreadyVisitedElements.add( element.getQualifiedName().toString() ) ) { + // If we already visited the element we should not go into it again. + // This can happen when diamond inheritance is used with interfaces + return; + } + addMethodNotYetOverridden( alreadyAdded, methodsIn( element.getEnclosedElements() ), parentType ); + + if ( hasNonObjectSuperclass( element ) ) { + addEnclosedMethodsInHierarchy( + alreadyAdded, + alreadyVisitedElements, + asTypeElement( element.getSuperclass() ), + parentType + ); + } + + for ( TypeMirror interfaceType : element.getInterfaces() ) { + addEnclosedMethodsInHierarchy( + alreadyAdded, + alreadyVisitedElements, + asTypeElement( interfaceType ), + parentType + ); + } + + } + + /** + * @param alreadyCollected methods that have already been collected and to which the not-yet-overridden methods will + * be added + * @param methodsToAdd methods to add to alreadyAdded, if they are not yet overridden by an element in the list + * @param parentType the type for with elements are collected + */ + private void addMethodNotYetOverridden(List alreadyCollected, + List methodsToAdd, + TypeElement parentType) { + List safeToAdd = new ArrayList<>( methodsToAdd.size() ); + for ( ExecutableElement toAdd : methodsToAdd ) { + if ( isNotPrivate( toAdd ) && isNotObjectEquals( toAdd ) + && methodWasNotYetOverridden( alreadyCollected, toAdd, parentType ) ) { + safeToAdd.add( toAdd ); + } + } + + alreadyCollected.addAll( 0, safeToAdd ); + } + + /** + * @param executable the executable to check + * @return {@code true}, iff the executable does not represent {@link java.lang.Object#equals(Object)} or an + * overridden version of it + */ + private boolean isNotObjectEquals(ExecutableElement executable) { + if ( executable.getSimpleName().contentEquals( "equals" ) && executable.getParameters().size() == 1 + && asTypeElement( executable.getParameters().get( 0 ).asType() ).getQualifiedName().contentEquals( + "java.lang.Object" + ) ) { + return false; + } + return true; + } + + /** + * @param alreadyCollected the list of already collected methods of one type hierarchy (order is from sub-types to + * super-types) + * @param executable the method to check + * @param parentType the type for which elements are collected + * @return {@code true}, iff the given executable was not yet overridden by a method in the given list. + */ + private boolean methodWasNotYetOverridden(List alreadyCollected, + ExecutableElement executable, TypeElement parentType) { + for ( ListIterator it = alreadyCollected.listIterator(); it.hasNext(); ) { + ExecutableElement executableInSubtype = it.next(); + if ( executableInSubtype == null ) { + continue; + } + if ( delegate.overrides( executableInSubtype, executable, parentType ) ) { + return false; + } + else if ( delegate.overrides( executable, executableInSubtype, parentType ) ) { + // remove the method from another interface hierarchy that is overridden by the executable to add + it.remove(); + return true; + } + } + + return true; + } + + private void addEnclosedFieldsInHierarchy( List alreadyAdded, + TypeElement element, TypeElement parentType) { + if ( element != parentType ) { // otherwise the element was already checked for replacement + element = replaceTypeElementIfNecessary( element ); + } + + if ( element.asType().getKind() == TypeKind.ERROR ) { + throw new TypeHierarchyErroneousException( element ); + } + + addFields( alreadyAdded, fieldsIn( element.getEnclosedElements() ) ); + + if ( hasNonObjectSuperclass( element ) ) { + addEnclosedFieldsInHierarchy( + alreadyAdded, + asTypeElement( element.getSuperclass() ), + parentType + ); + } + } + + private static void addFields(List alreadyCollected, List variablesToAdd) { + List safeToAdd = new ArrayList<>( variablesToAdd.size() ); + safeToAdd.addAll( variablesToAdd ); + + alreadyCollected.addAll( 0, safeToAdd ); + } + + /** + * @param element the type element to check + * @return {@code true}, iff the type has a super-class that is not java.lang.Object + */ + private boolean hasNonObjectSuperclass(TypeElement element) { + if ( element.getSuperclass().getKind() == TypeKind.ERROR ) { + throw new TypeHierarchyErroneousException( element ); + } + + return element.getSuperclass().getKind() == TypeKind.DECLARED + && !asTypeElement( element.getSuperclass() ).getQualifiedName().toString().equals( "java.lang.Object" ); + } + + /** + * @param mirror the type positionHint + * @return the corresponding type element + */ + private TypeElement asTypeElement(TypeMirror mirror) { + return (TypeElement) ( (DeclaredType) mirror ).asElement(); + } + + /** + * Checks whether the {@code executable} does not have a private modifier. + * + * @param executable the executable to check + * @return {@code true}, iff the executable does not have a private modifier + */ + private boolean isNotPrivate(ExecutableElement executable) { + return !executable.getModifiers().contains( Modifier.PRIVATE ); + } + + protected abstract TypeElement replaceTypeElementIfNecessary(TypeElement element); + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractTypeUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractTypeUtilsDecorator.java new file mode 100644 index 0000000000..2f7c4d604d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AbstractTypeUtilsDecorator.java @@ -0,0 +1,136 @@ +/* + * 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.util; + +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Types; + +/** + * Replaces the usage of {@link TypeUtils} within MapStruct by delegating to the original implementation or to our + * specific workarounds if necessary. + * + * @author Andreas Gudian + */ +public abstract class AbstractTypeUtilsDecorator implements TypeUtils { + + private final Types delegate; + + AbstractTypeUtilsDecorator(ProcessingEnvironment processingEnv) { + this.delegate = processingEnv.getTypeUtils(); + } + + @Override + public Element asElement(TypeMirror t) { + return delegate.asElement( t ); + } + + @Override + public boolean isSameType(TypeMirror t1, TypeMirror t2) { + return delegate.isSameType( t1, t2 ); + } + + @Override + public boolean isSubtype(TypeMirror t1, TypeMirror t2) { + return delegate.isSubtype( t1, t2 ); + } + + @Override + public boolean isAssignable(TypeMirror t1, TypeMirror t2) { + return delegate.isAssignable( t1, t2 ); + } + + @Override + public boolean contains(TypeMirror t1, TypeMirror t2) { + return delegate.contains( t1, t2 ); + } + + @Override + public boolean isSubsignature(ExecutableType m1, ExecutableType m2) { + return delegate.isSubsignature( m1, m2 ); + } + + @Override + public List directSupertypes(TypeMirror t) { + return delegate.directSupertypes( t ); + } + + @Override + public TypeMirror erasure(TypeMirror t) { + return delegate.erasure( t ); + } + + @Override + public TypeElement boxedClass(PrimitiveType p) { + return delegate.boxedClass( p ); + } + + @Override + public PrimitiveType unboxedType(TypeMirror t) { + return delegate.unboxedType( t ); + } + + @Override + public TypeMirror capture(TypeMirror t) { + return delegate.capture( t ); + } + + @Override + public PrimitiveType getPrimitiveType(TypeKind kind) { + return delegate.getPrimitiveType( kind ); + } + + @Override + public NullType getNullType() { + return delegate.getNullType(); + } + + @Override + public NoType getNoType(TypeKind kind) { + return delegate.getNoType( kind ); + } + + @Override + public ArrayType getArrayType(TypeMirror componentType) { + return delegate.getArrayType( componentType ); + } + + @Override + public WildcardType getWildcardType(TypeMirror extendsBound, TypeMirror superBound) { + return delegate.getWildcardType( extendsBound, superBound ); + } + + @Override + public DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs) { + return delegate.getDeclaredType( typeElem, typeArgs ); + } + + @Override + public DeclaredType getDeclaredType(DeclaredType containing, TypeElement typeElem, TypeMirror... typeArgs) { + return delegate.getDeclaredType( containing, typeElem, typeArgs ); + } + + @Override + public TypeMirror asMemberOf(DeclaredType containing, Element element) { + return delegate.asMemberOf( containing, element ); + } + + @Override + public boolean isSubtypeErased(TypeMirror t1, TypeMirror t2) { + return delegate.isSubtype( erasure( t1 ), erasure( t2 ) ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java new file mode 100644 index 0000000000..8025acfeb1 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java @@ -0,0 +1,112 @@ +/* + * 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.util; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; + +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.MethodType; + +import static org.mapstruct.ap.internal.util.Executables.isPublicNotStatic; + +/** + * Utils for working with the {@link AccessorNamingStrategy}. + * + * @author Filip Hrisafov + */ +public final class AccessorNamingUtils { + + private final AccessorNamingStrategy accessorNamingStrategy; + + public AccessorNamingUtils(AccessorNamingStrategy accessorNamingStrategy) { + this.accessorNamingStrategy = accessorNamingStrategy; + } + + public boolean isGetterMethod(ExecutableElement executable) { + return executable != null && isPublicNotStatic( executable ) && + executable.getParameters().isEmpty() && + accessorNamingStrategy.getMethodType( executable ) == MethodType.GETTER; + } + + public boolean isPresenceCheckMethod(ExecutableElement executable) { + + return executable != null + && isPublicNotStatic( executable ) + && executable.getParameters().isEmpty() + && ( executable.getReturnType().getKind() == TypeKind.BOOLEAN || + "java.lang.Boolean".equals( getQualifiedName( executable.getReturnType() ) ) ) + && accessorNamingStrategy.getMethodType( executable ) == MethodType.PRESENCE_CHECKER; + } + + public boolean isSetterMethod(ExecutableElement executable) { + return executable != null + && isPublicNotStatic( executable ) + && executable.getParameters().size() == 1 + && accessorNamingStrategy.getMethodType( executable ) == MethodType.SETTER; + } + + public boolean isAdderMethod(ExecutableElement executable) { + return executable != null + && isPublicNotStatic( executable ) + && executable.getParameters().size() == 1 + && accessorNamingStrategy.getMethodType( executable ) == MethodType.ADDER; + } + + public String getPropertyName(ExecutableElement executable) { + return accessorNamingStrategy.getPropertyName( executable ); + } + + /** + * @param adderMethod the adder method + * + * @return the 'element name' to which an adder method applies. If. e.g. an adder method is named + * {@code addChild(Child v)}, the element name would be 'Child'. + */ + public String getElementNameForAdder(Accessor adderMethod) { + if ( adderMethod.getAccessorType() == AccessorType.ADDER ) { + return accessorNamingStrategy.getElementName( (ExecutableElement) adderMethod.getElement() ); + } + else { + return null; + } + } + + private static String getQualifiedName(TypeMirror type) { + DeclaredType declaredType = type.accept( + new SimpleTypeVisitor8() { + @Override + public DeclaredType visitDeclared(DeclaredType t, Void p) { + return t; + } + }, + null + ); + + if ( declaredType == null ) { + return null; + } + + TypeElement typeElement = declaredType.asElement().accept( + new SimpleElementVisitor8() { + @Override + public TypeElement visitType(TypeElement e, Void p) { + return e; + } + }, + null + ); + + return typeElement != null ? typeElement.getQualifiedName().toString() : null; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessingException.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessingException.java index 25d73eaba5..29c73d2691 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessingException.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessingException.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.util; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java new file mode 100644 index 0000000000..9f8523efa0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java @@ -0,0 +1,288 @@ +/* + * 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.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; +import javax.annotation.processing.Messager; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; + +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.DefaultAccessorNamingStrategy; +import org.mapstruct.ap.spi.DefaultBuilderProvider; +import org.mapstruct.ap.spi.DefaultEnumMappingStrategy; +import org.mapstruct.ap.spi.EnumMappingStrategy; +import org.mapstruct.ap.spi.EnumTransformationStrategy; +import org.mapstruct.ap.spi.FreeBuilderAccessorNamingStrategy; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; +import org.mapstruct.ap.spi.MapStructProcessingEnvironment; +import org.mapstruct.ap.spi.NoOpBuilderProvider; + +/** + * Keeps contextual data in the scope of the entire annotation processor ("application scope"). + * + * @author Gunnar Morling + */ +public class AnnotationProcessorContext implements MapStructProcessingEnvironment { + + private List astModifyingAnnotationProcessors; + + private BuilderProvider builderProvider; + private AccessorNamingStrategy accessorNamingStrategy; + private EnumMappingStrategy enumMappingStrategy; + private boolean initialized; + private Map enumTransformationStrategies; + + private AccessorNamingUtils accessorNaming; + private NullabilityResolver nullabilityResolver; + private Elements elementUtils; + private Types typeUtils; + private Messager messager; + private boolean disableBuilder; + private boolean disableJSpecify; + private boolean verbose; + + private Map options; + + public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean disableBuilder, + boolean disableJSpecify, boolean verbose, Map options) { + astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList( + findAstModifyingAnnotationProcessors( messager ) ); + this.elementUtils = elementUtils; + this.typeUtils = typeUtils; + this.messager = messager; + this.disableBuilder = disableBuilder; + this.disableJSpecify = disableJSpecify; + this.verbose = verbose; + this.options = java.util.Collections.unmodifiableMap( options ); + } + + /** + * Method for initializing the context with the SPIs. The reason why we do this is due to the fact that + * when custom SPI implementations are done and users don't set {@code proc:none} then our processor + * would be triggered. And this context will always get initialized and the SPI won't be found. However, + * if this is lazily evaluated it won't be a problem, as in the SPI implementation module there won't be any + * processing done. + */ + private void initialize() { + if ( initialized ) { + return; + } + + AccessorNamingStrategy defaultAccessorNamingStrategy; + BuilderProvider defaultBuilderProvider; + if ( elementUtils.getTypeElement( ImmutablesConstants.IMMUTABLE_FQN ) != null ) { + defaultAccessorNamingStrategy = new ImmutablesAccessorNamingStrategy(); + defaultBuilderProvider = new ImmutablesBuilderProvider(); + if ( verbose ) { + messager.printMessage( Diagnostic.Kind.NOTE, "MapStruct: Immutables found on classpath" ); + } + } + else if ( elementUtils.getTypeElement( FreeBuilderConstants.FREE_BUILDER_FQN ) != null ) { + defaultAccessorNamingStrategy = new FreeBuilderAccessorNamingStrategy(); + defaultBuilderProvider = new DefaultBuilderProvider(); + if ( verbose ) { + messager.printMessage( Diagnostic.Kind.NOTE, "MapStruct: Freebuilder found on classpath" ); + } + } + else { + defaultAccessorNamingStrategy = new DefaultAccessorNamingStrategy(); + defaultBuilderProvider = new DefaultBuilderProvider(); + } + this.accessorNamingStrategy = Services.get( AccessorNamingStrategy.class, defaultAccessorNamingStrategy ); + this.accessorNamingStrategy.init( this ); + if ( verbose ) { + messager.printMessage( + Diagnostic.Kind.NOTE, + "MapStruct: Using accessor naming strategy: " + + this.accessorNamingStrategy.getClass().getCanonicalName() + ); + } + this.builderProvider = this.disableBuilder ? + new NoOpBuilderProvider() : + Services.get( BuilderProvider.class, defaultBuilderProvider ); + this.builderProvider.init( this ); + if ( verbose ) { + messager.printMessage( + Diagnostic.Kind.NOTE, + "MapStruct: Using builder provider: " + this.builderProvider.getClass().getCanonicalName() + ); + } + this.accessorNaming = new AccessorNamingUtils( this.accessorNamingStrategy ); + this.nullabilityResolver = new NullabilityResolver( !this.disableJSpecify ); + + this.enumMappingStrategy = Services.get( EnumMappingStrategy.class, new DefaultEnumMappingStrategy() ); + this.enumMappingStrategy.init( this ); + if ( verbose ) { + messager.printMessage( + Diagnostic.Kind.NOTE, + "MapStruct: Using enum naming strategy: " + + this.enumMappingStrategy.getClass().getCanonicalName() + ); + } + + this.enumTransformationStrategies = new LinkedHashMap<>(); + ServiceLoader transformationStrategiesLoader = ServiceLoader.load( + EnumTransformationStrategy.class, + AnnotationProcessorContext.class.getClassLoader() + ); + + for ( EnumTransformationStrategy transformationStrategy : transformationStrategiesLoader ) { + String transformationStrategyName = transformationStrategy.getStrategyName(); + if ( enumTransformationStrategies.containsKey( transformationStrategyName ) ) { + throw new IllegalStateException( + "Multiple EnumTransformationStrategies are using the same ma,e. Found: " + + enumTransformationStrategies.get( transformationStrategyName ) + " and " + + transformationStrategy + " for name " + transformationStrategyName ); + } + + transformationStrategy.init( this ); + enumTransformationStrategies.put( transformationStrategyName, transformationStrategy ); + } + + + this.initialized = true; + } + + private static List findAstModifyingAnnotationProcessors(Messager messager) { + List processors = new ArrayList<>(); + + ServiceLoader loader = ServiceLoader.load( + AstModifyingAnnotationProcessor.class, AnnotationProcessorContext.class.getClassLoader() + ); + + // Lombok packages an AstModifyingAnnotationProcessor as part of their jar + // this leads to problems within Eclipse when lombok is used as an agent + // Therefore we are wrapping this into an iterator that can handle exceptions by ignoring + // the faulty processor + Iterator loaderIterator = new FaultyDelegatingIterator( + messager, + loader.iterator() + ); + + while ( loaderIterator.hasNext() ) { + AstModifyingAnnotationProcessor processor = loaderIterator.next(); + if ( processor != null ) { + processors.add( processor ); + } + } + + return processors; + } + + private static class FaultyDelegatingIterator implements Iterator { + + private final Messager messager; + private final Iterator delegate; + + private FaultyDelegatingIterator(Messager messager, + Iterator delegate) { + this.messager = messager; + this.delegate = delegate; + } + + @Override + public boolean hasNext() { + // Check the delegate maximum of 5 times + // before returning false + int failures = 5; + while ( failures > 0 ) { + try { + return delegate.hasNext(); + } + catch ( Throwable t ) { + failures--; + logFailure( t ); + } + } + + return false; + } + + @Override + public AstModifyingAnnotationProcessor next() { + try { + return delegate.next(); + } + catch ( Throwable t ) { + logFailure( t ); + return null; + } + } + + private void logFailure(Throwable t) { + StringWriter sw = new StringWriter(); + t.printStackTrace( new PrintWriter( sw ) ); + + String reportableStacktrace = sw.toString().replace( System.lineSeparator(), " " ); + + messager.printMessage( + Diagnostic.Kind.WARNING, + "Failed to read AstModifyingAnnotationProcessor. Reading next processor. Reason: " + + reportableStacktrace + ); + } + } + + @Override + public Elements getElementUtils() { + return elementUtils; + } + + @Override + public Types getTypeUtils() { + return typeUtils; + } + + public List getAstModifyingAnnotationProcessors() { + return astModifyingAnnotationProcessors; + } + + public AccessorNamingUtils getAccessorNaming() { + initialize(); + return accessorNaming; + } + + public NullabilityResolver getNullabilityResolver() { + initialize(); + return nullabilityResolver; + } + + public AccessorNamingStrategy getAccessorNamingStrategy() { + initialize(); + return accessorNamingStrategy; + } + + public EnumMappingStrategy getEnumMappingStrategy() { + initialize(); + return enumMappingStrategy; + } + + public BuilderProvider getBuilderProvider() { + initialize(); + return builderProvider; + } + + public Map getEnumTransformationStrategies() { + initialize(); + return enumTransformationStrategies; + } + + public Map getOptions() { + return this.options; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Collections.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Collections.java index 0d12152019..365efc56ce 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Collections.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Collections.java @@ -1,28 +1,15 @@ -/** - * Copyright 2012-2015 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.util; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -35,36 +22,23 @@ public class Collections { private Collections() { } + @SafeVarargs public static Set asSet(T... elements) { - Set set = new HashSet(); - - for ( T element : elements ) { - set.add( element ); - } - + Set set = new HashSet<>( elements.length ); + java.util.Collections.addAll( set, elements ); return set; } - public static List newArrayList(T... elements) { - List list = new ArrayList(); - - list.addAll( Arrays.asList( elements ) ); - - return list; - } - + @SafeVarargs public static Set asSet(Collection collection, T... elements) { - Set set = new HashSet( collection ); - - for ( T element : elements ) { - set.add( element ); - } - + Set set = new HashSet<>( collection.size() + elements.length ); + java.util.Collections.addAll( set, elements ); return set; } + @SafeVarargs public static Set asSet(Collection collection, Collection... elements) { - Set set = new HashSet( collection ); + Set set = new HashSet<>( collection ); for ( Collection element : elements ) { set.addAll( element ); @@ -77,8 +51,12 @@ public static T first(Collection collection) { return collection.iterator().next(); } + public static T last(List list) { + return list.get( list.size() - 1 ); + } + public static List join(List a, List b) { - List result = new ArrayList( a.size() + b.size() ); + List result = new ArrayList<>( a.size() + b.size() ); result.addAll( a ); result.addAll( b ); @@ -86,14 +64,16 @@ public static List join(List a, List b) { return result; } - public static boolean hasNonNullElements(Iterable elements) { - if ( elements != null ) { - for ( E e : elements ) { - if ( e != null ) { - return true; - } - } - } - return false; + public static Map.Entry first(Map map) { + return map.entrySet().iterator().next(); } + + public static V firstValue(Map map) { + return first( map ).getValue(); + } + + public static K firstKey(Map map) { + return first( map ).getKey(); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseElementUtilsDecorator.java new file mode 100644 index 0000000000..3c1dc84d36 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseElementUtilsDecorator.java @@ -0,0 +1,40 @@ +/* + * 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.util; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; + +public class EclipseElementUtilsDecorator extends AbstractElementUtilsDecorator { + + private final Elements delegate; + + EclipseElementUtilsDecorator(ProcessingEnvironment processingEnv, TypeElement mapperElement) { + super( processingEnv, mapperElement ); + this.delegate = processingEnv.getElementUtils(); + } + + /** + * When running during Eclipse Incremental Compilation, we might get a TypeElement that has an UnresolvedTypeBinding + * and which is not automatically resolved. In that case, getEnclosedElements returns an empty list. We take that as + * a hint to check if the TypeElement resolved by FQN might have any enclosed elements and, if so, return the + * resolved element. + * + * @param element the original element + * @return the element freshly resolved using the qualified name, if the original element did not return any + * enclosed elements, whereas the resolved element does return enclosed elements. + */ + protected TypeElement replaceTypeElementIfNecessary(TypeElement element) { + if ( element.getEnclosedElements().isEmpty() ) { + TypeElement resolvedByName = delegate.getTypeElement( element.getQualifiedName() ); + if ( resolvedByName != null && !resolvedByName.getEnclosedElements().isEmpty() ) { + return resolvedByName; + } + } + return element; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseTypeUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseTypeUtilsDecorator.java new file mode 100644 index 0000000000..380df67bd5 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/EclipseTypeUtilsDecorator.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.ap.internal.util; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVariable; +import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Types; + +public class EclipseTypeUtilsDecorator extends AbstractTypeUtilsDecorator { + + private final Types delegate; + + EclipseTypeUtilsDecorator(ProcessingEnvironment processingEnv) { + super( processingEnv ); + this.delegate = processingEnv.getTypeUtils(); + } + + @Override + public boolean contains(TypeMirror t1, TypeMirror t2) { + if ( TypeKind.TYPEVAR == t2.getKind() ) { + return containsType( t1, ( (TypeVariable) t2 ).getLowerBound() ); + } + else { + return containsType( t1, t2 ); + } + } + + private boolean containsType(TypeMirror t1, TypeMirror t2) { + + boolean result = false; + if ( TypeKind.DECLARED == t2.getKind() ) { + if ( TypeKind.WILDCARD == t1.getKind() ) { + WildcardType wct = (WildcardType) t1; + if ( wct.getExtendsBound() != null ) { + result = isAssignable( t2, wct.getExtendsBound() ); + } + else if ( wct.getSuperBound() != null ) { + result = isAssignable( wct.getSuperBound(), t2 ); + } + else { + result = isAssignable( t2, wct ); + } + } + } + return result; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ElementUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ElementUtils.java new file mode 100644 index 0000000000..3e026cde80 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/ElementUtils.java @@ -0,0 +1,47 @@ +/* + * 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.util; + +import java.util.List; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.Elements; + +import org.mapstruct.ap.internal.version.VersionInformation; + +public interface ElementUtils extends Elements { + + static ElementUtils create(ProcessingEnvironment processingEnvironment, VersionInformation info, + TypeElement mapperElement) { + if ( info.isEclipseJDTCompiler() ) { + return new EclipseElementUtilsDecorator( processingEnvironment, mapperElement ); + } + else { + return new JavacElementUtilsDecorator( processingEnvironment, mapperElement ); + } + } + + /** + * Finds all executable elements within the given type element, including executable elements defined in super + * classes and implemented interfaces. Methods defined in {@link java.lang.Object}, + * implementations of {@link java.lang.Object#equals(Object)} and private methods are ignored + * + * @param element the element to inspect + * @return the executable elements usable in the type + */ + List getAllEnclosedExecutableElements(TypeElement element); + + /** + * Finds all variable elements within the given type element, including variable + * elements defined in super classes and implemented interfaces and including the fields in the . + * + * @param element the element to inspect + * @return the executable elements usable in the type + */ + List getAllEnclosedFields(TypeElement element); +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java index 8debfdd641..c845450e13 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Executables.java @@ -1,44 +1,16 @@ -/** - * Copyright 2012-2015 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.util; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; - -import org.mapstruct.ap.internal.naming.DefaultAccessorNamingStrategy; -import org.mapstruct.ap.internal.prism.AfterMappingPrism; -import org.mapstruct.ap.internal.prism.BeforeMappingPrism; -import org.mapstruct.ap.internal.services.Services; -import org.mapstruct.ap.spi.AccessorNamingStrategy; -import org.mapstruct.ap.spi.MethodType; -import static javax.lang.model.util.ElementFilter.methodsIn; -import static org.mapstruct.ap.internal.util.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary; +import org.mapstruct.ap.internal.gem.AfterMappingGem; +import org.mapstruct.ap.internal.gem.BeforeMappingGem; +import org.mapstruct.ap.internal.util.accessor.Accessor; /** * Provides functionality around {@link ExecutableElement}s. @@ -47,196 +19,27 @@ */ public class Executables { - private static final Method DEFAULT_METHOD; - - static { - Method method; - try { - method = ExecutableElement.class.getMethod( "isDefault" ); - } - catch ( NoSuchMethodException e ) { - method = null; - } - DEFAULT_METHOD = method; - } - - private static final AccessorNamingStrategy ACCESSOR_NAMING_STRATEGY = Services.get( - AccessorNamingStrategy.class, - new DefaultAccessorNamingStrategy() - ); - private Executables() { } - public static boolean isGetterMethod(ExecutableElement method) { - return isPublic( method ) && - method.getParameters().isEmpty() && - ACCESSOR_NAMING_STRATEGY.getMethodType( method ) == MethodType.GETTER; - } - - public static boolean isSetterMethod(ExecutableElement method) { - return isPublic( method ) - && method.getParameters().size() == 1 - && ACCESSOR_NAMING_STRATEGY.getMethodType( method ) == MethodType.SETTER; + static boolean isPublicNotStatic(ExecutableElement method) { + return isPublic( method ) && isNotStatic( method ); } - public static boolean isAdderMethod(ExecutableElement method) { - return isPublic( method ) - && method.getParameters().size() == 1 - && ACCESSOR_NAMING_STRATEGY.getMethodType( method ) == MethodType.ADDER; - } - - private static boolean isPublic(ExecutableElement method) { + static boolean isPublic(ExecutableElement method) { return method.getModifiers().contains( Modifier.PUBLIC ); } - public static String getPropertyName(ExecutableElement getterOrSetterMethod) { - return ACCESSOR_NAMING_STRATEGY.getPropertyName( getterOrSetterMethod ); - } - - public static boolean isDefaultMethod(ExecutableElement method) { - try { - return DEFAULT_METHOD != null && Boolean.TRUE.equals( DEFAULT_METHOD.invoke( method ) ); - } - catch ( IllegalAccessException e ) { - return false; - } - catch ( InvocationTargetException e ) { - return false; - } + private static boolean isNotStatic(ExecutableElement method) { + return !method.getModifiers().contains( Modifier.STATIC ); } - /** - * @param adderMethod the adder method - * @return the 'element name' to which an adder method applies. If. e.g. an adder method is named - * {@code addChild(Child v)}, the element name would be 'Child'. - */ - public static String getElementNameForAdder(ExecutableElement adderMethod) { - return ACCESSOR_NAMING_STRATEGY.getElementName( adderMethod ); + public static boolean isFinal(Accessor accessor) { + return accessor != null && accessor.getModifiers().contains( Modifier.FINAL ); } - public static String getCollectionGetterName(ExecutableElement targetSetter) { - String propertyName = ACCESSOR_NAMING_STRATEGY.getPropertyName( targetSetter ); - return ACCESSOR_NAMING_STRATEGY.getCollectionGetterName( propertyName ); - } - - /** - * @param mirror the type mirror - * - * @return the corresponding type element - */ - private static TypeElement asTypeElement(TypeMirror mirror) { - return (TypeElement) ( (DeclaredType) mirror ).asElement(); - } - - /** - * Finds all executable elements within the given type element, including executable elements defined in super - * classes and implemented interfaces. Methods defined in {@link java.lang.Object} are ignored, as well as - * implementations of {@link java.lang.Object#equals(Object)}. - * - * @param elementUtils element helper - * @param element the element to inspect - * - * @return the executable elements usable in the type - */ - public static List getAllEnclosedExecutableElements(Elements elementUtils, TypeElement element) { - List enclosedElements = new ArrayList(); - element = replaceTypeElementIfNecessary( elementUtils, element ); - addEnclosedElementsInHierarchy( elementUtils, enclosedElements, element, element ); - - return enclosedElements; - } - - private static void addEnclosedElementsInHierarchy(Elements elementUtils, List alreadyAdded, - TypeElement element, TypeElement parentType) { - if ( element != parentType ) { // otherwise the element was already checked for replacement - element = replaceTypeElementIfNecessary( elementUtils, element ); - } - - addNotYetOverridden( elementUtils, alreadyAdded, methodsIn( element.getEnclosedElements() ), parentType ); - - if ( hasNonObjectSuperclass( element ) ) { - addEnclosedElementsInHierarchy( - elementUtils, - alreadyAdded, - asTypeElement( element.getSuperclass() ), - parentType - ); - } - - for ( TypeMirror interfaceType : element.getInterfaces() ) { - addEnclosedElementsInHierarchy( - elementUtils, - alreadyAdded, - asTypeElement( interfaceType ), - parentType - ); - } - - } - - /** - * @param alreadyCollected methods that have already been collected and to which the not-yet-overridden methods will - * be added - * @param methodsToAdd methods to add to alreadyAdded, if they are not yet overridden by an element in the list - * @param parentType the type for with elements are collected - */ - private static void addNotYetOverridden(Elements elementUtils, List alreadyCollected, - List methodsToAdd, TypeElement parentType) { - List safeToAdd = new ArrayList( methodsToAdd.size() ); - for ( ExecutableElement toAdd : methodsToAdd ) { - if ( isNotObjectEquals( toAdd ) - && wasNotYetOverridden( elementUtils, alreadyCollected, toAdd, parentType ) ) { - safeToAdd.add( toAdd ); - } - } - - alreadyCollected.addAll( 0, safeToAdd ); - } - - - /** - * @param executable the executable to check - * - * @return {@code true}, iff the executable does not represent {@link java.lang.Object#equals(Object)} or an - * overridden version of it - */ - private static boolean isNotObjectEquals(ExecutableElement executable) { - if ( executable.getSimpleName().contentEquals( "equals" ) && executable.getParameters().size() == 1 - && asTypeElement( executable.getParameters().get( 0 ).asType() ).getQualifiedName().contentEquals( - "java.lang.Object" - ) ) { - return false; - } - return true; - } - - /** - * @param elementUtils the elementUtils - * @param alreadyAdded the list of already collected methods of one type hierarchy (order is from sub-types to - * super-types) - * @param executable the method to check - * @param parentType the type for which elements are collected - * @return {@code true}, iff the given executable was not yet overridden by a method in the given list. - */ - private static boolean wasNotYetOverridden(Elements elementUtils, List alreadyAdded, - ExecutableElement executable, TypeElement parentType) { - for ( ExecutableElement executableInSubtype : alreadyAdded ) { - if ( elementUtils.overrides( executableInSubtype, executable, parentType ) ) { - return false; - } - } - return true; - } - - /** - * @param element the type element to check - * - * @return {@code true}, iff the type has a super-class that is not java.lang.Object - */ - private static boolean hasNonObjectSuperclass(TypeElement element) { - return element.getSuperclass().getKind() == TypeKind.DECLARED - && asTypeElement( element.getSuperclass() ).getSuperclass().getKind() == TypeKind.DECLARED; + public static boolean isDefaultMethod(ExecutableElement method) { + return method.isDefault(); } /** @@ -253,7 +56,7 @@ public static boolean isLifecycleCallbackMethod(ExecutableElement executableElem * @return {@code true}, if the executable element is a method annotated with {@code @AfterMapping} */ public static boolean isAfterMappingMethod(ExecutableElement executableElement) { - return AfterMappingPrism.getInstanceOn( executableElement ) != null; + return AfterMappingGem.instanceOn( executableElement ) != null; } /** @@ -261,6 +64,6 @@ public static boolean isAfterMappingMethod(ExecutableElement executableElement) * @return {@code true}, if the executable element is a method annotated with {@code @BeforeMapping} */ public static boolean isBeforeMappingMethod(ExecutableElement executableElement) { - return BeforeMappingPrism.getInstanceOn( executableElement ) != null; + return BeforeMappingGem.instanceOn( executableElement ) != null; } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Extractor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Extractor.java new file mode 100644 index 0000000000..7d6a87e8a4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Extractor.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.ap.internal.util; + +/** + * This is a helper interface until we migrate to Java 8. It allows us to abstract our code easier. + * + * @param the type of the input to the function + * @param the type of the result of the function + * + * @author Filip Hrisafov + */ +public interface Extractor { + + /** + * Extract a value from the passed parameter. + * + * @param t the value that we need to extract from + * + * @return the result from the extraction + */ + R apply(T t); +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java new file mode 100644 index 0000000000..b2f8ac1442 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Fields.java @@ -0,0 +1,32 @@ +/* + * 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.util; + +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; + +/** + * Provides functionality around {@link VariableElement}s. + * + * @author Sjaak Derksen + */ +public class Fields { + + private Fields() { + } + + public static boolean isFieldAccessor(VariableElement method) { + return isPublic( method ) && isNotStatic( method ); + } + + static boolean isPublic(VariableElement method) { + return method.getModifiers().contains( Modifier.PUBLIC ); + } + + private static boolean isNotStatic(VariableElement method) { + return !method.getModifiers().contains( Modifier.STATIC ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java index b0110141ee..6492270705 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Filters.java @@ -1,71 +1,145 @@ -/** - * Copyright 2012-2015 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.util; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collection; +import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; - +import java.util.Map; +import java.util.function.BiFunction; +import java.util.stream.Collectors; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.ElementAccessor; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; + +import static org.mapstruct.ap.internal.util.Collections.first; +import static org.mapstruct.ap.internal.util.accessor.AccessorType.ADDER; +import static org.mapstruct.ap.internal.util.accessor.AccessorType.SETTER; /** * Filter methods for working with {@link Element} collections. * * @author Gunnar Morling + * @author Filip Hrisafov */ public class Filters { - private Filters() { + private static final Method RECORD_COMPONENTS_METHOD; + + static { + Method recordComponentsMethod; + try { + recordComponentsMethod = TypeElement.class.getMethod( "getRecordComponents" ); + } + catch ( NoSuchMethodException e ) { + recordComponentsMethod = null; + } + RECORD_COMPONENTS_METHOD = recordComponentsMethod; } - public static List getterMethodsIn(Iterable elements) { - List getterMethods = new LinkedList(); + private final AccessorNamingUtils accessorNaming; + private final TypeUtils typeUtils; + private final TypeMirror typeMirror; - for ( ExecutableElement method : elements ) { - if ( Executables.isGetterMethod( method ) ) { - getterMethods.add( method ); - } - } + public Filters(AccessorNamingUtils accessorNaming, TypeUtils typeUtils, TypeMirror typeMirror) { + this.accessorNaming = accessorNaming; + this.typeUtils = typeUtils; + this.typeMirror = typeMirror; + } - return getterMethods; + public List getterMethodsIn(List elements) { + return elements.stream() + .filter( accessorNaming::isGetterMethod ) + .map( method -> ReadAccessor.fromGetter( method, getReturnType( method ) ) ) + .collect( Collectors.toCollection( LinkedList::new ) ); } - public static List setterMethodsIn(Iterable elements) { - List setterMethods = new LinkedList(); + @SuppressWarnings("unchecked") + public List recordComponentsIn(TypeElement typeElement) { + if ( RECORD_COMPONENTS_METHOD == null ) { + return java.util.Collections.emptyList(); + } - for ( ExecutableElement method : elements ) { - if ( Executables.isSetterMethod( method ) ) { - setterMethods.add( method ); - } + try { + return (List) RECORD_COMPONENTS_METHOD.invoke( typeElement ); + } + catch ( IllegalAccessException | InvocationTargetException e ) { + return java.util.Collections.emptyList(); } - return setterMethods; } - public static List adderMethodsIn(Iterable elements) { - List adderMethods = new LinkedList(); - - for ( ExecutableElement method : elements ) { - if ( Executables.isAdderMethod( method ) ) { - adderMethods.add( method ); - } + public Map recordAccessorsIn(Collection recordComponents) { + if ( recordComponents.isEmpty() ) { + return java.util.Collections.emptyMap(); } + Map recordAccessors = new LinkedHashMap<>(); + for ( Element recordComponent : recordComponents ) { + recordAccessors.put( + recordComponent.getSimpleName().toString(), + ReadAccessor.fromRecordComponent( + recordComponent, + typeUtils.asMemberOf( (DeclaredType) typeMirror, recordComponent ) + ) + ); + } + + return recordAccessors; + } + + private TypeMirror getReturnType(ExecutableElement executableElement) { + return getWithinContext( executableElement ).getReturnType(); + } + + public List fieldsIn(List accessors, BiFunction creator) { + return accessors.stream() + .filter( Fields::isFieldAccessor ) + .map( variableElement -> creator.apply( variableElement, getWithinContext( variableElement ) ) ) + .collect( Collectors.toCollection( LinkedList::new ) ); + } + + public List presenceCheckMethodsIn(List elements) { + return elements.stream() + .filter( accessorNaming::isPresenceCheckMethod ) + .collect( Collectors.toCollection( LinkedList::new ) ); + } + + public List setterMethodsIn(List elements) { + return elements.stream() + .filter( accessorNaming::isSetterMethod ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), SETTER ) ) + .collect( Collectors.toCollection( LinkedList::new ) ); + } + + private TypeMirror getFirstParameter(ExecutableElement executableElement) { + return first( getWithinContext( executableElement ).getParameterTypes() ); + } + + private ExecutableType getWithinContext( ExecutableElement executableElement ) { + return (ExecutableType) typeUtils.asMemberOf( (DeclaredType) typeMirror, executableElement ); + } + + private TypeMirror getWithinContext( VariableElement variableElement ) { + return typeUtils.asMemberOf( (DeclaredType) typeMirror, variableElement ); + } - return adderMethods; + public List adderMethodsIn(List elements) { + return elements.stream() + .filter( accessorNaming::isAdderMethod ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), ADDER ) ) + .collect( Collectors.toCollection( LinkedList::new ) ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/FormattingMessager.java b/processor/src/main/java/org/mapstruct/ap/internal/util/FormattingMessager.java index fc2d4f05c9..6f3b1dc7a9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/FormattingMessager.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/FormattingMessager.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.util; @@ -52,10 +39,10 @@ public interface FormattingMessager { /** * Prints a message of the specified kind at the location of the - * annotation mirror of the annotated element. + * annotation positionHint of the annotated element. * * @param e the annotated element - * @param a the annotation to use as a position hint + * @param a the annotation to use as a position hint (can be null) * @param msg the message * @param args Arguments referenced by the format specifiers in the format string. If there are more arguments * than format specifiers, the extra arguments are ignored @@ -65,7 +52,7 @@ public interface FormattingMessager { /** * Prints a message of the specified kind at the location of the - * annotation value inside the annotation mirror of the annotated + * annotation value inside the annotation positionHint of the annotated * element. * * @param e the annotated element @@ -80,4 +67,14 @@ void printMessage(Element e, AnnotationValue v, Message msg, Object... args); + + /** + * Just log as plain note + * @param level nesting level + * @param log the log message + * @param args the arguments + */ + void note(int level, Message log, Object... args); + + boolean isErroneous(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/FreeBuilderConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/FreeBuilderConstants.java new file mode 100644 index 0000000000..e7ae4d63d4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/FreeBuilderConstants.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.ap.internal.util; + +/** + * Helper for holding FreeBuilder FQN. + * + * @author Filip Hrisafov + */ +public class FreeBuilderConstants { + + public static final String FREE_BUILDER_FQN = "org.inferred.freebuilder.FreeBuilder"; + + private FreeBuilderConstants() { + + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/IgnoreJRERequirement.java b/processor/src/main/java/org/mapstruct/ap/internal/util/IgnoreJRERequirement.java new file mode 100644 index 0000000000..565ebcce12 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/IgnoreJRERequirement.java @@ -0,0 +1,16 @@ +/* + * 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.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.CLASS) +@Target({ ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE }) +public @interface IgnoreJRERequirement { +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/ImmutablesConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ImmutablesConstants.java new file mode 100644 index 0000000000..4e670d17d0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/ImmutablesConstants.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.ap.internal.util; + +/** + * Helper for holding Immutables FQN. + * + * @author Filip Hrisafov + */ +public class ImmutablesConstants { + + public static final String IMMUTABLE_FQN = "org.immutables.value.Value.Immutable"; + + private ImmutablesConstants() { + + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JSpecifyConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JSpecifyConstants.java new file mode 100644 index 0000000000..7836e0deba --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JSpecifyConstants.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.ap.internal.util; + +/** + * Helper holding constants for working with JSpecify null annotations. + * + * @author Filip Hrisafov + */ +public final class JSpecifyConstants { + + /** + * Fully qualified name of {@code org.jspecify.annotations.Nullable}. + */ + public static final String NULLABLE_FQN = "org.jspecify.annotations.Nullable"; + + /** + * Fully qualified name of {@code org.jspecify.annotations.NonNull}. + */ + public static final String NON_NULL_FQN = "org.jspecify.annotations.NonNull"; + + /** + * Fully qualified name of {@code org.jspecify.annotations.NullMarked}. Its presence on an + * element (or an enclosing element closer than any {@code @NullUnmarked} one) means unannotated + * types in that scope are effectively {@code @NonNull}. + */ + public static final String NULL_MARKED_FQN = "org.jspecify.annotations.NullMarked"; + + /** + * Fully qualified name of {@code org.jspecify.annotations.NullUnmarked}. Its presence on an + * element (or an enclosing element closer than any {@code @NullMarked} one) reverts the scope + * back to unknown nullability. + */ + public static final String NULL_UNMARKED_FQN = "org.jspecify.annotations.NullUnmarked"; + + private JSpecifyConstants() { + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java new file mode 100644 index 0000000000..68d556c542 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.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.ap.internal.util; + +/** + * Helper holding Java collections full qualified class names for conversion registration, + * to achieve Java compatibility. + * + * @author Cause Chung + */ +public final class JavaCollectionConstants { + public static final String SEQUENCED_MAP_FQN = "java.util.SequencedMap"; + public static final String SEQUENCED_SET_FQN = "java.util.SequencedSet"; + + private JavaCollectionConstants() { + + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavaStreamConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaStreamConstants.java new file mode 100644 index 0000000000..acd63abcd7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaStreamConstants.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.ap.internal.util; + +/** + * Helper holding Java Stream full qualified class names for conversion registration + * + * @author Filip Hrisafov + */ +public final class JavaStreamConstants { + + public static final String STREAM_FQN = "java.util.stream.Stream"; + + private JavaStreamConstants() { + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavaTimeConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaTimeConstants.java deleted file mode 100644 index f198ac7ac9..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/JavaTimeConstants.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.util; - -/** - * Helper holding Java time full qualified class names for conversion registration - */ -public final class JavaTimeConstants { - - public static final String ZONED_DATE_TIME_FQN = "java.time.ZonedDateTime"; - public static final String LOCAL_DATE_TIME_FQN = "java.time.LocalDateTime"; - public static final String LOCAL_DATE_FQN = "java.time.LocalDate"; - public static final String LOCAL_TIME_FQN = "java.time.LocalTime"; - public static final String DATE_TIME_FORMATTER_FQN = "java.time.format.DateTimeFormatter"; - - private JavaTimeConstants() { - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavacElementUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavacElementUtilsDecorator.java new file mode 100644 index 0000000000..fa0e46171b --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavacElementUtilsDecorator.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.ap.internal.util; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; + +public class JavacElementUtilsDecorator extends AbstractElementUtilsDecorator { + + JavacElementUtilsDecorator(ProcessingEnvironment processingEnv, TypeElement mapperElement) { + super( processingEnv, mapperElement ); + } + + @Override + protected TypeElement replaceTypeElementIfNecessary(TypeElement element) { + return element; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavacTypeUtilsDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavacTypeUtilsDecorator.java new file mode 100644 index 0000000000..8b3454e87e --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavacTypeUtilsDecorator.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.ap.internal.util; + +import javax.annotation.processing.ProcessingEnvironment; + +public class JavacTypeUtilsDecorator extends AbstractTypeUtilsDecorator { + + JavacTypeUtilsDecorator(ProcessingEnvironment processingEnv) { + super( processingEnv ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java index f693f01573..c89877062e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JaxbConstants.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.util; @@ -23,8 +10,10 @@ */ public final class JaxbConstants { - public static final String JAXB_ELEMENT_FQN = "javax.xml.bind.JAXBElement"; + public static final String JAVAX_JAXB_ELEMENT_FQN = "javax.xml.bind.JAXBElement"; + public static final String JAKARTA_JAXB_ELEMENT_FQN = "jakarta.xml.bind.JAXBElement"; private JaxbConstants() { } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JodaTimeConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JodaTimeConstants.java index d3e9c4da89..5300f1bf91 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/JodaTimeConstants.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JodaTimeConstants.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.util; @@ -38,5 +25,7 @@ private JodaTimeConstants() { public static final String DATE_TIME_FORMAT_FQN = "org.joda.time.format.DateTimeFormat"; + public static final String DATE_TIME_ZONE_FQN = "org.joda.time.DateTimeZone"; + public static final String DATE_TIME_FORMAT = "LL"; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java deleted file mode 100644 index e79a6fe8f7..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.util; - -import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.MapperConfigPrism; -import org.mapstruct.ap.internal.prism.MapperPrism; -import org.mapstruct.ap.internal.prism.MappingInheritanceStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -/** - * Provides an aggregated view to the settings given via {@link org.mapstruct.Mapper} and - * {@link org.mapstruct.MapperConfig} for a specific mapper class. - *

      - * Settings given via {@code Mapper} will generally take precedence over settings inherited from a referenced config - * class. The lists of referenced mappers given via {@link org.mapstruct.Mapper#uses()} and - * {@link org.mapstruct.MapperConfig#uses() } will be merged. - * - * @author Sjaak Derksen - */ -public class MapperConfiguration { - - private final MapperPrism mapperPrism; - private final MapperConfigPrism mapperConfigPrism; - - public static MapperConfiguration getInstanceOn(Element e) { - return new MapperConfiguration( MapperPrism.getInstanceOn( e ) ); - } - - private MapperConfiguration(MapperPrism mapperPrism) { - this.mapperPrism = mapperPrism; - TypeMirror typeMirror = mapperPrism.config(); - if ( typeMirror.getKind().equals( TypeKind.DECLARED ) ) { - this.mapperConfigPrism = MapperConfigPrism.getInstanceOn( ( (DeclaredType) typeMirror ).asElement() ); - } - else { - this.mapperConfigPrism = null; - } - } - - public String implementationName() { - if ( mapperConfigPrism != null && mapperPrism.values.implementationName() == null ) { - return mapperConfigPrism.implementationName(); - } - else { - return mapperPrism.implementationName(); - } - } - - public String implementationPackage() { - if ( mapperConfigPrism != null && mapperPrism.values.implementationPackage() == null ) { - return mapperConfigPrism.implementationPackage(); - } - else { - return mapperPrism.implementationPackage(); - } - } - - public List uses() { - Set uses = new LinkedHashSet( mapperPrism.uses() ); - if ( mapperConfigPrism != null ) { - uses.addAll( mapperConfigPrism.uses() ); - } - return new ArrayList( uses ); - } - - public List imports() { - return mapperPrism.imports(); - } - - public String unmappedTargetPolicy() { - if ( mapperConfigPrism != null && mapperPrism.values.unmappedTargetPolicy() == null ) { - return mapperConfigPrism.unmappedTargetPolicy(); - } - else { - return mapperPrism.unmappedTargetPolicy(); - } - } - - public CollectionMappingStrategyPrism getCollectionMappingStrategy() { - if ( mapperConfigPrism != null && mapperPrism.values.collectionMappingStrategy() == null ) { - return CollectionMappingStrategyPrism.valueOf( mapperConfigPrism.collectionMappingStrategy() ); - } - else { - return CollectionMappingStrategyPrism.valueOf( mapperPrism.collectionMappingStrategy() ); - } - } - - public MappingInheritanceStrategyPrism getMappingInheritanceStrategy() { - if ( mapperConfigPrism != null && mapperPrism.values.mappingInheritanceStrategy() == null ) { - return MappingInheritanceStrategyPrism.valueOf( mapperConfigPrism.mappingInheritanceStrategy() ); - } - else { - return MappingInheritanceStrategyPrism.valueOf( mapperPrism.mappingInheritanceStrategy() ); - } - } - - public boolean isMapToDefault(NullValueMappingStrategyPrism mapNullToDefault) { - - // check on method level - if ( mapNullToDefault != null ) { - return mapNullToDefault == NullValueMappingStrategyPrism.RETURN_DEFAULT; - } - - return isMapToDefaultOnMapperAndMappingConfigLevel(); - - } - - private boolean isMapToDefaultOnMapperAndMappingConfigLevel() { - final NullValueMappingStrategyPrism strategy; - if ( mapperConfigPrism != null && mapperPrism.values.nullValueMappingStrategy() == null ) { - strategy = NullValueMappingStrategyPrism.valueOf( mapperConfigPrism.nullValueMappingStrategy() ); - } - else { - strategy = NullValueMappingStrategyPrism.valueOf( mapperPrism.nullValueMappingStrategy() ); - } - - return NullValueMappingStrategyPrism.RETURN_DEFAULT == strategy; - } - - - public String componentModel() { - if ( mapperConfigPrism != null && mapperPrism.values.componentModel() == null ) { - return mapperConfigPrism.componentModel(); - } - else { - return mapperPrism.componentModel(); - } - } - - public TypeMirror getMapperConfigMirror() { - return mapperPrism.config(); - } - - public boolean isValid() { - return mapperPrism.isValid; - } - - public boolean isSetUnmappedTargetPolicy() { - return mapperPrism.values.unmappedTargetPolicy() != null; - } - - public AnnotationMirror getAnnotationMirror() { - return mapperPrism.mirror; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index cba508ab1d..acac07b4b3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -1,25 +1,15 @@ -/** - * Copyright 2012-2015 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.util; import javax.tools.Diagnostic; +import static org.mapstruct.ap.internal.util.MessageConstants.FAQ_AMBIGUOUS_URL; +import static org.mapstruct.ap.internal.util.MessageConstants.FAQ_QUALIFIER_URL; + /** * A message used in warnings/errors raised by the annotation processor. * @@ -28,97 +18,238 @@ public enum Message { // CHECKSTYLE:OFF - BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ), + PROCESSING_NOTE( "processing: %s.", Diagnostic.Kind.NOTE ), + CONFIG_NOTE( "applying mapper configuration: %s.", Diagnostic.Kind.NOTE ), + MESSAGE_MOVED_TO_MAPPER_ERROR( "%s Occured at '%s' in '%s'." ), + MESSAGE_MOVED_TO_MAPPER_WARNING( "%s Occured at '%s' in '%s'.", Diagnostic.Kind.WARNING ), + + BEANMAPPING_CREATE_NOTE( "creating bean mapping method implementation for %s.", Diagnostic.Kind.NOTE ), + BEANMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'nullValuePropertyMappingStrategy', 'resultType' and 'qualifiedBy' are undefined in @BeanMapping, define at least one of them." ), BEANMAPPING_NOT_ASSIGNABLE( "%s not assignable to: %s." ), - BEANMAPPING_UNKNOWN_PROPERTY_IN_RETURNTYPE( "Unknown property \"%s\" in return type." ), + BEANMAPPING_ABSTRACT( "The result type %s may not be an abstract class nor interface." ), + BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE( "Unknown property \"%s\" in result type %s. Did you mean \"%s\"?" ), + BEANMAPPING_UNKNOWN_PROPERTY_IN_TYPE( "Unknown property \"%s\" in type %s for target name \"%s\". Did you mean \"%s\"?" ), + BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE( "Property \"%s\" has no write accessor in %s." ), + BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_TYPE( "Property \"%s\" has no write accessor in %s for target name \"%s\"." ), BEANMAPPING_SEVERAL_POSSIBLE_SOURCES( "Several possible source properties for target property \"%s\"." ), BEANMAPPING_SEVERAL_POSSIBLE_TARGET_ACCESSORS( "Found several matching getters for property \"%s\"." ), BEANMAPPING_UNMAPPED_TARGETS_WARNING( "Unmapped target %s.", Diagnostic.Kind.WARNING ), BEANMAPPING_UNMAPPED_TARGETS_ERROR( "Unmapped target %s." ), + BEANMAPPING_UNMAPPED_FORGED_TARGETS_WARNING( "Unmapped target %s. Mapping from %s to %s.", Diagnostic.Kind.WARNING ), + BEANMAPPING_UNMAPPED_FORGED_TARGETS_ERROR( "Unmapped target %s. Mapping from %s to %s." ), + BEANMAPPING_UNMAPPED_SOURCES_WARNING( "Unmapped source %s.", Diagnostic.Kind.WARNING ), + BEANMAPPING_UNMAPPED_SOURCES_ERROR( "Unmapped source %s." ), + BEANMAPPING_UNMAPPED_FORGED_SOURCES_WARNING( "Unmapped source %s. Mapping from %s to %s.", Diagnostic.Kind.WARNING ), + BEANMAPPING_UNMAPPED_FORGED_SOURCES_ERROR( "Unmapped source %s. Mapping from %s to %s." ), + BEANMAPPING_MISSING_IGNORED_SOURCES_ERROR( "Ignored unknown source %s." ), + BEANMAPPING_REDUNDANT_IGNORED_SOURCES_ERROR( "Source %s mapped despite being listed in ignoreUnmappedSourceProperties." ), + BEANMAPPING_REDUNDANT_IGNORED_SOURCES_WARNING( "Source %s mapped despite being listed in ignoreUnmappedSourceProperties.", Diagnostic.Kind.WARNING ), BEANMAPPING_CYCLE_BETWEEN_PROPERTIES( "Cycle(s) between properties given via dependsOn(): %s." ), BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ), + BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ), + BEANMAPPING_UNKNOWN_PROPERTY_IN_IGNORED("No property named \"%s\" exists in @Ignored for target type \"%s\". Did you mean \"%s\"?"), + CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ), + CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ), + CONDITION_PROPERTIES_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#PROPERTIES. Only source, @Context, @MappingTarget, @TargetType, @TargetPropertyName and @SourcePropertyName parameters are allowed for conditions applicable to properties." ), + + PROPERTYMAPPING_MAPPING_NOTE( "mapping property: %s to: %s.", Diagnostic.Kind.NOTE ), + PROPERTYMAPPING_CREATE_NOTE( "creating property mapping: %s.", Diagnostic.Kind.NOTE ), + PROPERTYMAPPING_SELECT_NOTE( "selecting property mapping: %s.", Diagnostic.Kind.NOTE ), PROPERTYMAPPING_MAPPING_NOT_FOUND( "Can't map %s to \"%s %s\". Consider to declare/implement a mapping method: \"%s map(%s value)\"." ), + PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND( "No target bean properties found: can't map %s to \"%s %s\". Consider to declare/implement a mapping method: \"%s map(%s value)\"." ), + PROPERTYMAPPING_FORGED_MAPPING_NOT_FOUND( "No target bean properties found: can't map %s to %s. Consider to implement a mapping method: \"%s map(%s value)\"." ), PROPERTYMAPPING_DUPLICATE_TARGETS( "Target property \"%s\" must not be mapped more than once." ), PROPERTYMAPPING_EMPTY_TARGET( "Target must not be empty in @Mapping." ), PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED( "Source and constant are both defined in @Mapping, either define a source or a constant." ), + PROPERTYMAPPING_SOURCE_AND_IGNORE_BOTH_DEFINED( "Source and ignore are both defined in @Mapping, make explicit in reverse mapping when the intent is to ignore the reverse mapping." ), PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED( "Source and expression are both defined in @Mapping, either define a source or an expression." ), PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED( "Expression and constant are both defined in @Mapping, either define an expression or a constant." ), PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED( "Expression and default value are both defined in @Mapping, either define a defaultValue or an expression." ), PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED( "Constant and default value are both defined in @Mapping, either define a defaultValue or a constant." ), - PROPERTYMAPPING_INVALID_EXPRESSION( "Value must be given in the form \"java()\"." ), - PROPERTYMAPPING_REVERSAL_PROBLEM( "Parameter %s cannot be reversed." ), - PROPERTYMAPPING_INVALID_PARAMETER_NAME( "Method has no parameter named \"%s\"." ), + PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Expression and default expression are both defined in @Mapping, either define an expression or a default expression." ), + PROPERTYMAPPING_EXPRESSION_AND_CONDITION_EXPRESSION_BOTH_DEFINED( "Expression and condition expression are both defined in @Mapping, either define an expression or a condition expression." ), + PROPERTYMAPPING_CONSTANT_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Constant and default expression are both defined in @Mapping, either define a constant or a default expression." ), + PROPERTYMAPPING_CONSTANT_AND_CONDITION_EXPRESSION_BOTH_DEFINED( "Constant and condition expression are both defined in @Mapping, either define a constant or a condition expression." ), + PROPERTYMAPPING_DEFAULT_VALUE_AND_DEFAULT_EXPRESSION_BOTH_DEFINED( "Default value and default expression are both defined in @Mapping, either define a default value or a default expression." ), + PROPERTYMAPPING_DEFAULT_VALUE_AND_NVPMS( "Default value and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultValue or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_EXPRESSION_VALUE_AND_NVPMS( "Expression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define an expression or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_CONSTANT_VALUE_AND_NVPMS( "Constant and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a constant or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS( "DefaultExpression and nullValuePropertyMappingStrategy are both defined in @Mapping, either define a defaultExpression or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_IGNORE_AND_NVPMS( "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, either define ignore or an nullValuePropertyMappingStrategy." ), + PROPERTYMAPPING_TARGET_THIS_AND_IGNORE( "Using @Mapping( target = \".\", ignore = true ) is not allowed. You need to use @BeanMapping( ignoreByDefault = true ) if you would like to ignore all non explicitly mapped target properties." ), + PROPERTYMAPPING_TARGET_THIS_NO_SOURCE( "Using @Mapping( target = \".\") requires a source property. Expression or constant cannot be used as a source."), + PROPERTYMAPPING_EXPRESSION_AND_QUALIFIER_BOTH_DEFINED("Expression and a qualifier both defined in @Mapping, either define an expression or a qualifier."), + PROPERTYMAPPING_INVALID_EXPRESSION( "Value for expression must be given in the form \"java()\"." ), + PROPERTYMAPPING_INVALID_DEFAULT_EXPRESSION( "Value for default expression must be given in the form \"java()\"." ), + PROPERTYMAPPING_INVALID_CONDITION_EXPRESSION( "Value for condition expression must be given in the form \"java()\"." ), + PROPERTYMAPPING_INVALID_PARAMETER_NAME( "Method has no source parameter named \"%s\". Method source parameters are: \"%s\"." ), PROPERTYMAPPING_NO_PROPERTY_IN_PARAMETER( "The type of parameter \"%s\" has no property named \"%s\"." ), - PROPERTYMAPPING_INVALID_PROPERTY_NAME( "No property named \"%s\" exists in source parameter(s)." ), + PROPERTYMAPPING_INVALID_PROPERTY_NAME( "No property named \"%s\" exists in source parameter(s). Did you mean \"%s\"?" ), + PROPERTYMAPPING_INVALID_PROPERTY_NAME_SOURCE_HAS_NO_PROPERTIES( "No property named \"%s\" exists in source parameter(s). Type \"%s\" has no properties." ), + PROPERTYMAPPING_NO_PRESENCE_CHECKER_FOR_SOURCE_TYPE( "Using custom source value presence checking strategy, but no presence checker found for %s in source type." ), PROPERTYMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE( "No read accessor found for property \"%s\" in target type." ), + PROPERTYMAPPING_NO_WRITE_ACCESSOR_FOR_TARGET_TYPE( "No write accessor found for property \"%s\" in target type." ), + PROPERTYMAPPING_WHITESPACE_TRIMMED( "The property named \"%s\" has whitespaces, using trimmed property \"%s\" instead.", Diagnostic.Kind.WARNING ), + PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PROPERTY_FROM_TARGET("The type of parameter \"%s\" has no property named \"%s\". Please define the source property explicitly."), + PROPERTYMAPPING_CANNOT_DETERMINE_SOURCE_PARAMETER_FROM_TARGET("No property named \"%s\" exists in source parameter(s). Please define the source explicitly."), + PROPERTYMAPPING_NO_SUITABLE_COLLECTION_OR_MAP_CONSTRUCTOR( "%s does not have an accessible copy or no-args constructor." ), + PROPERTYMAPPING_NO_ACCESSIBLE_PARAMETERLESS_CONSTRUCTOR( "%s does not have an accessible parameterless constructor. Either change the nullValuePropertyMappingStrategy or define a defaultValue or a defaultExpression." ), + PROPERTYMAPPING_EXPRESSION_AND_CONDITION_QUALIFIED_BY_NAME_BOTH_DEFINED( "Expression and condition qualified by name are both defined in @Mapping, either define an expression or a condition qualified by name." ), + PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES( "No target property found for target \"%s\".", Diagnostic.Kind.WARNING ), + PROPERTYMAPPING_NULLABLE_SOURCE_TO_NON_NULL_CONSTRUCTOR_PARAM( "Can't map potentially nullable source property \"%s\" to @NonNull constructor parameter \"%s\". Consider adding a defaultValue or defaultExpression." ), + PROPERTYMAPPING_JSPECIFY_SKIP_NULL_CHECK_NON_NULL_SOURCE( "JSpecify skipping null check for property \"%s\": source is @NonNull.", Diagnostic.Kind.NOTE ), + PROPERTYMAPPING_JSPECIFY_ADD_NULL_CHECK( "JSpecify adding null check for property \"%s\": source=%s, target=%s.", Diagnostic.Kind.NOTE ), + PROPERTYMAPPING_JSPECIFY_SKIP_NULL_CHECK( "JSpecify skipping null check for property \"%s\": source=%s, target=%s.", Diagnostic.Kind.NOTE ), + PROPERTYMAPPING_JSPECIFY_SKIP_METHOD_GUARD_NON_NULL_PARAM( "JSpecify skipping method-level null guard for property \"%s\": parameter is @NonNull.", Diagnostic.Kind.NOTE ), + MAPPING_METHOD_JSPECIFY_FORCE_RETURN_DEFAULT( "JSpecify forcing NullValueMappingStrategy.RETURN_DEFAULT for method \"%s\": return type is @NonNull.", Diagnostic.Kind.NOTE ), + + CONVERSION_LOSSY_WARNING( "%s has a possibly lossy conversion from %s to %s.", Diagnostic.Kind.WARNING ), + CONVERSION_LOSSY_ERROR( "Can't map %s. It has a possibly lossy conversion from %s to %s." ), - CONSTANTMAPPING_MAPPING_NOT_FOUND( "Can't map \"%s %s\" to \"%s %s\"." ), + CONSTANTMAPPING_MAPPING_NOT_FOUND( "Can't map %s to \"%s %s\"." ), + CONSTANTMAPPING_MAPPING_NOT_FOUND_WITH_DETAILS( "Can't map %s to \"%s %s\". Reason: %s." ), CONSTANTMAPPING_NO_READ_ACCESSOR_FOR_TARGET_TYPE( "No read accessor found for property \"%s\" in target type." ), + CONSTANTMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s for property \"%s\"." ), + MAPMAPPING_CREATE_NOTE( "creating map mapping method implementation for %s.", Diagnostic.Kind.NOTE ), MAPMAPPING_KEY_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source key type to target key type." ), MAPMAPPING_VALUE_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source value type to target value type." ), MAPMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy', 'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', 'valueDateFormat', 'valueQualfiedBy' and 'valueTargetType' are all undefined in @MapMapping, define at least one of them." ), + MAPMAPPING_SELECT_KEY_NOTE( "selecting key mapping: %s.", Diagnostic.Kind.NOTE ), + MAPMAPPING_SELECT_VALUE_NOTE( "selecting value mapping: %s.", Diagnostic.Kind.NOTE ), + MAPMAPPING_CREATE_KEY_NOTE( "creating key mapping: %s.", Diagnostic.Kind.NOTE ), + MAPMAPPING_CREATE_VALUE_NOTE( "creating value mapping: %s.", Diagnostic.Kind.NOTE ), + STREAMMAPPING_CREATE_NOTE( "creating stream mapping method implementation for %s.", Diagnostic.Kind.NOTE ), + ITERABLEMAPPING_CREATE_NOTE( "creating iterable mapping method implementation for %s.", Diagnostic.Kind.NOTE ), + ITERABLEMAPPING_SELECT_ELEMENT_NOTE( "selecting element mapping: %s.", Diagnostic.Kind.NOTE ), + ITERABLEMAPPING_CREATE_ELEMENT_NOTE( "creating element mapping: %s.", Diagnostic.Kind.NOTE ), ITERABLEMAPPING_MAPPING_NOT_FOUND( "No implementation can be generated for this method. Found no method nor implicit conversion for mapping source element type into target element type." ), ITERABLEMAPPING_NO_ELEMENTS( "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are undefined in @IterableMapping, define at least one of them." ), - ENUMMAPPING_MULTIPLE_TARGETS( "One enum constant must not be mapped to more than one target constant, but constant %s is mapped to %s." ), + ENUMMAPPING_MULTIPLE_SOURCES( "One enum constant must not be mapped to more than one target constant, but constant %s is mapped to %s." ), ENUMMAPPING_UNDEFINED_SOURCE( "A source constant must be specified for mappings of an enum mapping method." ), ENUMMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." ), ENUMMAPPING_UNDEFINED_TARGET( "A target constant must be specified for mappings of an enum mapping method." ), - ENUMMAPPING_UNMAPPED_TARGETS( "The following constants from the source enum have no corresponding constant in the target enum and must be be mapped via @Mapping: %s." ), + ENUMMAPPING_UNMAPPED_SOURCES( "The following constants from the source enum have no corresponding constant in the target enum and must be be mapped via adding additional mappings: %s." ), + ENUMMAPPING_REMOVED( "Mapping of Enums via @Mapping is removed. Please use @ValueMapping instead!" ), + ENUMMAPPING_INCORRECT_TRANSFORMATION_STRATEGY( "There is no registered EnumTransformationStrategy for '%s'. Registered strategies are: %s." ), + ENUMMAPPING_MISSING_CONFIGURATION( "Configuration has to be defined when strategy is defined." ), + ENUMMAPPING_NO_ELEMENTS( "'nameTransformationStrategy', 'configuration' and 'unexpectedValueMappingException' are undefined in @EnumMapping, define at least one of them." ), + ENUMMAPPING_ILLEGAL_TRANSFORMATION( "Illegal transformation for '%s' EnumTransformationStrategy. Error: '%s'." ), + + SUBCLASSMAPPING_DOUBLE_SOURCE_SUBCLASS( "Subclass '%s' is already defined as a source." ), + SUBCLASSMAPPING_ILLEGAL_SUBCLASS( "Class '%s' is not a subclass of '%s'." ), + SUBCLASSMAPPING_NO_VALID_SUPERCLASS( "Could not find a parameter that is a superclass for '%s'." ), + SUBCLASSMAPPING_UPDATE_METHODS_NOT_SUPPORTED( "SubclassMapping annotation can not be used for update methods." ), + SUBCLASSMAPPING_ILLOGICAL_ORDER( "SubclassMapping annotation for '%s' found after '%s', but all '%s' objects are also instances of '%s'.", Diagnostic.Kind.WARNING ), + + LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS( "Lifecycle method has multiple matching parameters (e. g. same type), in this case please ensure to name the parameters in the lifecycle and mapping method identical. This lifecycle method will not be used for the mapping method '%s'.", Diagnostic.Kind.WARNING), DECORATOR_NO_SUBTYPE( "Specified decorator type is no subtype of the annotated mapper type." ), DECORATOR_CONSTRUCTOR( "Specified decorator type has no default constructor nor a constructor with a single parameter accepting the decorated mapper type." ), + JAVADOC_NO_ELEMENTS( "'value', 'authors', 'deprecated' and 'since' are undefined in @Javadoc, define at least one of them." ), + + GENERAL_CANNOT_IMPLEMENT_PRIVATE_MAPPER("Cannot create an implementation for mapper %s, because it is a private %s."), GENERAL_NO_IMPLEMENTATION( "No implementation type is registered for return type %s." ), - GENERAL_AMBIGIOUS_MAPPING_METHOD( "Ambiguous mapping methods found for mapping %s to %s: %s." ), - GENERAL_AMBIGIOUS_FACTORY_METHOD( "Ambiguous factory methods found for creating %s: %s." ), + GENERAL_ABSTRACT_RETURN_TYPE( "The return type %s is an abstract class or interface. Provide a non abstract / non interface result type or a factory method." ), + GENERAL_AMBIGUOUS_MAPPING_METHOD( "Ambiguous mapping methods found for mapping %s to %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), + GENERAL_AMBIGUOUS_FACTORY_METHOD( "Ambiguous factory methods found for creating %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), + GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD( "Ambiguous presence check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), + GENERAL_AMBIGUOUS_SOURCE_PARAMETER_CHECK_METHOD( "Ambiguous source parameter check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ), + GENERAL_AMBIGUOUS_CONSTRUCTORS( "Ambiguous constructors found for creating %s: %s. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default." ), + GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS( "Incorrect @ConstructorProperties for %s. The size of the @ConstructorProperties does not match the number of constructor parameters" ), GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK( "No dateFormat check is supported for types %s, %s" ), GENERAL_VALID_DATE( "Given date format \"%s\" is valid.", Diagnostic.Kind.NOTE ), GENERAL_INVALID_DATE( "Given date format \"%s\" is invalid. Message: \"%s\"." ), + GENERAL_JODA_NOT_ON_CLASSPATH( "Cannot validate Joda dateformat, no Joda on classpath. Consider adding Joda to the annotation processorpath.", Diagnostic.Kind.WARNING ), + GENERAL_NOT_ALL_FORGED_CREATED( "Internal Error in creation of Forged Methods, it was expected all Forged Methods to finished with creation, but %s did not" ), + GENERAL_NO_SUITABLE_CONSTRUCTOR( "%s does not have an accessible constructor." ), + GENERAL_NO_QUALIFYING_METHOD_ANNOTATION( "Qualifier error. No method found annotated with: [ %s ]. See " + FAQ_QUALIFIER_URL + " for more info." ), + GENERAL_NO_QUALIFYING_METHOD_NAMED( "Qualifier error. No method found annotated with @Named#value: [ %s ]. See " + FAQ_QUALIFIER_URL + " for more info." ), + GENERAL_NO_QUALIFYING_METHOD_COMBINED( "Qualifier error. No method found annotated with @Named#value: [ %s ], annotated with [ %s ]. See " + FAQ_QUALIFIER_URL + " for more info." ), + + GENERAL_AMBIGUOUS_MAPPING_METHODY_METHODX( "Ambiguous 2step methods found, mapping %s to %s. Found methodY( methodX ( parameter ) ): %s." ), + GENERAL_AMBIGUOUS_MAPPING_CONVERSIONY_METHODX( "Ambiguous 2step methods found, mapping %s to %s. Found conversionY( methodX ( parameter ) ): %s." ), + GENERAL_AMBIGUOUS_MAPPING_METHODY_CONVERSIONX( "Ambiguous 2step methods found, mapping %s to %s. Found methodY( conversionX ( parameter ) ): %s." ), + + BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD( "More than one builder creation method for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ), + BUILDER_NO_BUILD_METHOD_FOUND("No build method \"%s\" found in \"%s\" for \"%s\". Found methods: \"%s\".", Diagnostic.Kind.ERROR ), + BUILDER_NO_BUILD_METHOD_FOUND_DEFAULT("No build method \"%s\" found in \"%s\" for \"%s\". Found methods: \"%s\". Consider to add @Builder in order to select the correct build method.", Diagnostic.Kind.ERROR ), RETRIEVAL_NO_INPUT_ARGS( "Can't generate mapping method with no input arguments." ), RETRIEVAL_DUPLICATE_MAPPING_TARGETS( "Can't generate mapping method with more than one @MappingTarget parameter." ), RETRIEVAL_VOID_MAPPING_METHOD( "Can't generate mapping method with return type void." ), - RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE( "The result type is not assignable to the the return type." ), - RETRIEVAL_ITERABLE_TO_NON_ITERABLE( "Can't generate mapping method from iterable type to non-iterable type." ), + RETRIEVAL_NON_ASSIGNABLE_RESULTTYPE( "The result type is not assignable to the return type." ), + RETRIEVAL_ITERABLE_TO_NON_ITERABLE( "Can't generate mapping method from iterable type from java stdlib to non-iterable type." ), RETRIEVAL_MAPPING_HAS_TARGET_TYPE_PARAMETER( "Can't generate mapping method that has a parameter annotated with @TargetType." ), - RETRIEVAL_NON_ITERABLE_TO_ITERABLE( "Can't generate mapping method from non-iterable type to iterable type." ), + RETRIEVAL_NON_ITERABLE_TO_ITERABLE( "Can't generate mapping method from non-iterable type to iterable type from java stdlib." ), + RETRIEVAL_NON_ITERABLE_TO_ARRAY( "Can't generate mapping method from non-iterable type to array." ), RETRIEVAL_PRIMITIVE_PARAMETER( "Can't generate mapping method with primitive parameter type." ), RETRIEVAL_PRIMITIVE_RETURN( "Can't generate mapping method with primitive return type." ), - RETRIEVAL_ENUM_TO_NON_ENUM( "Can't generate mapping method from enum type to non-enum type." ), - RETRIEVAL_NON_ENUM_TO_ENUM( "Can't generate mapping method from non-enum type to enum type." ), RETRIEVAL_TYPE_VAR_SOURCE( "Can't generate mapping method for a generic type variable source." ), RETRIEVAL_TYPE_VAR_RESULT( "Can't generate mapping method for a generic type variable target." ), RETRIEVAL_WILDCARD_SUPER_BOUND_SOURCE( "Can't generate mapping method for a wildcard super bound source." ), RETRIEVAL_WILDCARD_EXTENDS_BOUND_RESULT( "Can't generate mapping method for a wildcard extends bound result." ), + RETRIEVAL_CONTEXT_PARAMS_WITH_SAME_TYPE( "The types of @Context parameters must be unique." ), + RETRIEVAL_MAPPER_USES_CYCLE( "The mapper %s is referenced itself in Mapper#uses.", Diagnostic.Kind.WARNING ), + RETRIEVAL_AFTER_METHOD_NOT_IMPLEMENTED( "@AfterMapping can only be applied to an implemented method." ), + RETRIEVAL_BEFORE_METHOD_NOT_IMPLEMENTED( "@BeforeMapping can only be applied to an implemented method." ), + RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE( "@SourcePropertyName can only by applied to a String parameter." ), + RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only by applied to a String parameter." ), - - - INHERITCONFIGURATION_BOTH( "Method cannot be annotated with both a @InheritConfiguration and @InheritInverseConfiguration." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), INHERITINVERSECONFIGURATION_INVALID_NAME( "None of the candidates %s() matches given name: \"%s\"." ), - INHERITINVERSECONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s()." ), + INHERITINVERSECONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s." ), INHERITINVERSECONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ), INHERITCONFIGURATION_DUPLICATES( "Several matching methods exist: %s(). Specify a name explicitly." ), INHERITCONFIGURATION_INVALIDNAME( "None of the candidates %s() matches given name: \"%s\"." ), - INHERITCONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s()." ), + INHERITCONFIGURATION_DUPLICATE_MATCHES( "Given name \"%s\" matches several candidate methods: %s." ), INHERITCONFIGURATION_NO_NAME_MATCH( "Given name \"%s\" does not match the only candidate. Did you mean: \"%s\"." ), INHERITCONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH( "More than one configuration prototype method is applicable. Use @InheritConfiguration to select one of them explicitly: %s." ), - INHERITCONFIGURATION_CYCLE( "Cycle detected while evaluating inherited configurations. Inheritance path: %s" ); + INHERITINVERSECONFIGURATION_MULTIPLE_PROTOTYPE_METHODS_MATCH( "More than one configuration prototype method is applicable. Use @InheritInverseConfiguration to select one of them explicitly: %s." ), + INHERITCONFIGURATION_CYCLE( "Cycle detected while evaluating inherited configurations. Inheritance path: %s" ), + + VALUEMAPPING_CREATE_NOTE( "creating value mapping method implementation for %s.", Diagnostic.Kind.NOTE ), + VALUEMAPPING_DUPLICATE_SOURCE( "Source value mapping: \"%s\" cannot be mapped more than once." ), + VALUEMAPPING_ANY_AREADY_DEFINED( "Source = \"\" or \"\" can only be used once." ), + VALUEMAPPING_UNMAPPED_SOURCES( "The following constants from the %s enum have no corresponding constant in the %s enum and must be be mapped via adding additional mappings: %s." ), + VALUEMAPPING_ANY_REMAINING_FOR_NON_ENUM( "Source = \"\" can only be used on targets of type enum and not for %s." ), + VALUEMAPPING_ANY_REMAINING_OR_UNMAPPED_MISSING( "Source = \"\" or \"\" is advisable for mapping of type String to an enum type.", Diagnostic.Kind.WARNING ), + VALUEMAPPING_NON_EXISTING_CONSTANT_FROM_SPI( "Constant %s doesn't exist in enum type %s. Constant was returned from EnumMappingStrategy: %s"), + VALUEMAPPING_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." ), + VALUEMAPPING_THROW_EXCEPTION_SOURCE( "Source = \"\" is not allowed. Target = \"\" can only be used." ), + + MAPTOBEANMAPPING_WRONG_KEY_TYPE( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map but it was typed with %s.", Diagnostic.Kind.WARNING ), + MAPTOBEANMAPPING_RAW_MAP( "The Map parameter \"%s\" cannot be used for property mapping. It must be typed with Map but it was raw.", Diagnostic.Kind.WARNING ), + + ANNOTATE_WITH_MISSING_REQUIRED_PARAMETER( "Parameter \"%s\" is required for annotation \"%s\"." ), + ANNOTATE_WITH_UNKNOWN_PARAMETER( "Unknown parameter \"%s\" for annotation \"%s\". Did you mean \"%s\"?" ), + ANNOTATE_WITH_DUPLICATE_PARAMETER( "Parameter \"%s\" must not be defined more than once." ), + ANNOTATE_WITH_WRONG_PARAMETER( "Parameter \"%s\" is not of type \"%s\" but of type \"%s\" for annotation \"%s\"." ), + ANNOTATE_WITH_TOO_MANY_VALUE_TYPES( "Parameter \"%s\" has too many value types supplied, type \"%s\" is expected for annotation \"%s\"." ), + ANNOTATE_WITH_PARAMETER_ARRAY_NOT_EXPECTED( "Parameter \"%s\" does not accept multiple values for annotation \"%s\"." ), + ANNOTATE_WITH_NOT_ALLOWED_ON_CLASS( "Annotation \"%s\" is not allowed on classes." ), + ANNOTATE_WITH_NOT_ALLOWED_ON_METHODS( "Annotation \"%s\" is not allowed on methods." ), + ANNOTATE_WITH_ENUM_VALUE_DOES_NOT_EXIST( "Enum \"%s\" does not have value \"%s\"." ), + ANNOTATE_WITH_ENUM_CLASS_NOT_DEFINED( "enumClass needs to be defined when using enums." ), + ANNOTATE_WITH_ENUMS_NOT_DEFINED( "enums needs to be defined when using enumClass." ), + ANNOTATE_WITH_ANNOTATION_IS_NOT_REPEATABLE( "Annotation \"%s\" is not repeatable." ), + ANNOTATE_WITH_DUPLICATE( "Annotation \"%s\" is already present with the same elements configuration.", Diagnostic.Kind.WARNING ), + ; // CHECKSTYLE:ON private final String description; private final Diagnostic.Kind kind; - private Message(String description) { + Message(String description) { this.description = description; this.kind = Diagnostic.Kind.ERROR; } - private Message(String description, Diagnostic.Kind kind) { + Message(String description, Diagnostic.Kind kind) { this.description = description; this.kind = kind; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MessageConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MessageConstants.java new file mode 100644 index 0000000000..6b4603af19 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MessageConstants.java @@ -0,0 +1,16 @@ +/* + * 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.util; + +public final class MessageConstants { + + public static final String AND = " and "; + public static final String FAQ_QUALIFIER_URL = "https://mapstruct.org/faq/#qualifier"; + public static final String FAQ_AMBIGUOUS_URL = "https://mapstruct.org/faq/#ambiguous"; + + private MessageConstants() { + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java new file mode 100644 index 0000000000..96768daf56 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.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.ap.internal.util; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.tools.gem.Gem; + +/** + * A base helper class that provides utility methods for working with meta annotations. + * + * @param The type of the processed annotation + * @param The type of the underlying holder for the processed annotation + * + * @author Filip Hrisafov + */ +public abstract class MetaAnnotations { + + private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation"; + + private final ElementUtils elementUtils; + private final String annotationFqn; + + protected MetaAnnotations(ElementUtils elementUtils, String annotationFqn) { + this.elementUtils = elementUtils; + this.annotationFqn = annotationFqn; + } + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @return The processed annotations for the given element + */ + public Set getProcessedAnnotations(Element source) { + return getValues( source, source, new LinkedHashSet<>(), new HashSet<>() ); + } + + protected abstract G instanceOn(Element element); + + protected abstract void addInstance(G gem, Element source, Set values); + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @param element Element of interest: method, or (meta) annotation + * @param values the set of values found so far + * @param handledElements The collection of already handled elements to handle recursion correctly. + * @return The processed annotations for the given element + */ + private Set getValues(Element source, Element element, Set values, Set handledElements) { + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element annotationElement = annotationMirror.getAnnotationType().asElement(); + if ( isAnnotation( annotationElement, annotationFqn ) ) { + G gem = instanceOn( element ); + addInstance( gem, source, values ); + } + else if ( isNotJavaAnnotation( element ) && !handledElements.contains( annotationElement ) ) { + handledElements.add( annotationElement ); + getValues( source, annotationElement, values, handledElements ); + } + } + return values; + } + + private boolean isNotJavaAnnotation(Element element) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return !elementUtils.getPackageOf( element ).getQualifiedName().contentEquals( JAVA_LANG_ANNOTATION_PGK ); + } + return true; + } + + private boolean isAnnotation(Element element, String annotationFqn) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return ( (TypeElement) element ).getQualifiedName().contentEquals( annotationFqn ); + } + + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java index fcbc3651e0..ff01ae0ef3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NativeTypes.java @@ -1,30 +1,23 @@ -/** - * Copyright 2012-2015 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.util; +import java.math.BigDecimal; +import java.math.BigInteger; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import javax.lang.model.type.TypeKind; /** - * Provides functionality around the Java primitive data types and their wrapper - * types. + * Provides functionality around the Java primitive data types and their wrapper types. They are considered native. * * @author Gunnar Morling */ @@ -32,12 +25,373 @@ public class NativeTypes { private static final Map, Class> WRAPPER_TO_PRIMITIVE_TYPES; private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPES; + private static final Set> NUMBER_TYPES = new HashSet<>(); + private static final Map TYPE_KIND_NAME = new EnumMap<>( TypeKind.class ); + private static final Map ANALYZERS; + + private static final Map NARROWING_LUT; + + private static final Pattern PTRN_HEX = Pattern.compile( "^0[x|X].*" ); + private static final Pattern PTRN_OCT = Pattern.compile( "^0_*[0-7].*" ); + private static final Pattern PTRN_BIN = Pattern.compile( "^0[b|B].*" ); + private static final Pattern PTRN_FLOAT_DEC_ZERO = Pattern.compile( "^[^eE]*[1-9].*[eE]?.*" ); + private static final Pattern PTRN_FLOAT_HEX_ZERO = Pattern.compile( "^[^pP]*[1-9a-fA-F].*[pP]?.*" ); + + private static final Pattern PTRN_SIGN = Pattern.compile( "^[\\+|-]" ); + + private static final Pattern PTRN_LONG = Pattern.compile( "[l|L]$" ); + private static final Pattern PTRN_FLOAT = Pattern.compile( "[f|F]$" ); + private static final Pattern PTRN_DOUBLE = Pattern.compile( "[d|D]$" ); + + private static final Pattern PTRN_FAULTY_UNDERSCORE_INT = Pattern.compile( "^_|_$|-_|_-|\\+_|_\\+" ); + private static final Pattern PTRN_FAULTY_UNDERSCORE_FLOAT = Pattern.compile( "^_|_$|-_|_-|\\+_|_\\+|\\._|_\\." ); + private static final Pattern PTRN_FAULTY_DEC_UNDERSCORE_FLOAT = Pattern.compile( "_e|_E|e_|E_" ); + private static final Pattern PTRN_FAULTY_HEX_UNDERSCORE_FLOAT = Pattern.compile( "_p|_P|p_|P_" ); + + private interface LiteralAnalyzer { + + void validate(String s); + + Class getLiteral(); + + } + + private abstract static class NumberRepresentation { + + int radix; + String val; + boolean isIntegralType; + boolean isLong; + boolean isFloat; + + NumberRepresentation(String in, boolean isIntegralType, boolean isLong, boolean isFloat) { + this.isLong = isLong; + this.isFloat = isFloat; + this.isIntegralType = isIntegralType; + + String valWithoutSign; + boolean isNegative = in.startsWith( "-" ); + boolean hasSign = PTRN_SIGN.matcher( in ).find(); + if ( hasSign ) { + valWithoutSign = in.substring( 1 ); + } + else { + valWithoutSign = in; + } + if ( PTRN_HEX.matcher( valWithoutSign ).matches() ) { + // hex + radix = 16; + val = (isNegative ? "-" : "") + valWithoutSign.substring( 2 ); + } + else if ( PTRN_BIN.matcher( valWithoutSign ).matches() ) { + // binary + radix = 2; + val = (isNegative ? "-" : "") + valWithoutSign.substring( 2 ); + } + else if ( PTRN_OCT.matcher( valWithoutSign ).matches() ) { + // octal + radix = 8; + val = (isNegative ? "-" : "") + valWithoutSign.substring( 1 ); + } + else { + // decimal + radix = 10; + val = (isNegative ? "-" : "") + valWithoutSign; + } + } + + abstract void parse(String val, int radix); + + void validate() { + strip(); + parse( val, radix ); + } + + void strip() { + if ( isIntegralType ) { + removeAndValidateIntegerLiteralSuffix(); + removeAndValidateIntegerLiteralUnderscore(); + } + else { + removeAndValidateFloatingPointLiteralSuffix(); + removeAndValidateFloatingPointLiteralUnderscore(); + } + } + + /** + * remove java7+ underscores from the input + */ + void removeAndValidateIntegerLiteralUnderscore() { + if ( PTRN_FAULTY_UNDERSCORE_INT.matcher( val ).find() ) { + throw new NumberFormatException( "improperly placed underscores" ); + } + else { + val = val.replace( "_", "" ); + } + } + + /** + * remove java7+ underscores from the input + */ + void removeAndValidateFloatingPointLiteralUnderscore() { + boolean isHex = radix == 16; + if ( PTRN_FAULTY_UNDERSCORE_FLOAT.matcher( val ).find() + || !isHex && PTRN_FAULTY_DEC_UNDERSCORE_FLOAT.matcher( val ).find() + || isHex && PTRN_FAULTY_HEX_UNDERSCORE_FLOAT.matcher( val ).find() ) { + throw new NumberFormatException( "improperly placed underscores" ); + } + else { + val = val.replace( "_", "" ); + } + } + + /** + * + */ + void removeAndValidateIntegerLiteralSuffix() { + boolean endsWithLSuffix = PTRN_LONG.matcher( val ).find(); + // error handling + if ( endsWithLSuffix && !isLong ) { + throw new NumberFormatException( "L/l not allowed for non-long types" ); + } + if ( !endsWithLSuffix && isLong ) { + throw new NumberFormatException( "L/l mandatory for long types" ); + } + // remove suffix + if ( endsWithLSuffix ) { + val = val.substring( 0, val.length() - 1 ); + } + + } + + /** + * Double suffix forbidden for float. + * + */ + void removeAndValidateFloatingPointLiteralSuffix() { + boolean endsWithLSuffix = PTRN_LONG.matcher( val ).find(); + boolean endsWithFSuffix = PTRN_FLOAT.matcher( val ).find(); + boolean endsWithDSuffix = PTRN_DOUBLE.matcher( val ).find(); + // error handling + if ( isFloat && endsWithDSuffix ) { + throw new NumberFormatException( "Assigning double to a float" ); + } + // remove suffix + if ( endsWithLSuffix || endsWithFSuffix || endsWithDSuffix ) { + val = val.substring( 0, val.length() - 1 ); + } + } + + boolean floatHasBecomeZero(float parsed) { + if ( parsed == 0f ) { + return floatHasBecomeZero(); + } + else { + return false; + } + } + + boolean doubleHasBecomeZero(double parsed) { + if ( parsed == 0d ) { + return floatHasBecomeZero(); + } + else { + return false; + } + } + + private boolean floatHasBecomeZero() { + if ( radix == 10 ) { + // decimal, should be at least some number before exponent (eE) unequal to 0. + return PTRN_FLOAT_DEC_ZERO.matcher( val ).matches(); + } + else { + // hex, should be at least some number before exponent (pP) unequal to 0. + return PTRN_FLOAT_HEX_ZERO.matcher( val ).matches(); + } + } + } + + private static class BooleanAnalyzer implements LiteralAnalyzer { + + @Override + public void validate(String s) { + if ( !( "true".equals( s ) || "false".equals( s ) ) ) { + throw new IllegalArgumentException("only 'true' or 'false' are supported"); + } + } + + @Override + public Class getLiteral() { + return boolean.class; + } + } + + private static class CharAnalyzer implements LiteralAnalyzer { + + @Override + public void validate(String s) { + if ( !(s.length() == 3 && s.startsWith( "'" ) && s.endsWith( "'" )) ) { + throw new NumberFormatException( "invalid character literal" ); + } + } + + @Override + public Class getLiteral() { + return char.class; + } + } + + private static class ByteAnalyzer implements LiteralAnalyzer { + + @Override + public void validate(String s) { + NumberRepresentation br = new NumberRepresentation( s, true, false, false ) { + + @Override + void parse(String val, int radix) { + Byte.parseByte( val, radix ); + } + }; + br.validate(); + } + + @Override + public Class getLiteral() { + return int.class; + } + } + + private static class DoubleAnalyzer implements LiteralAnalyzer { + + @Override + public void validate(String s) { + NumberRepresentation br = new NumberRepresentation( s, false, false, false ) { + + @Override + void parse(String val, int radix) { + double d = Double.parseDouble( radix == 16 ? "0x" + val : val ); + if ( doubleHasBecomeZero( d ) ) { + throw new NumberFormatException( "floating point number too small" ); + } + if ( Double.isInfinite( d ) ) { + throw new NumberFormatException( "infinitive is not allowed" ); + } + } + }; + br.validate(); + } + + @Override + public Class getLiteral() { + return double.class; + } + + } + + private static class FloatAnalyzer implements LiteralAnalyzer { + + @Override + public void validate(String s) { + + NumberRepresentation br = new NumberRepresentation( s, false, false, true ) { + @Override + void parse(String val, int radix) { + float f = Float.parseFloat( radix == 16 ? "0x" + val : val ); + if ( doubleHasBecomeZero( f ) ) { + throw new NumberFormatException( "floating point number too small" ); + } + if ( Float.isInfinite( f ) ) { + throw new NumberFormatException( "infinitive is not allowed" ); + } + } + }; + br.validate(); + } + + @Override + public Class getLiteral() { + return float.class; + } + } + + private static class IntAnalyzer implements LiteralAnalyzer { + + @Override + public void validate(String s) { + NumberRepresentation br = new NumberRepresentation( s, true, false, false ) { + + @Override + void parse(String val, int radix) { + if ( radix == 10 ) { + // when decimal: treat like signed + Integer.parseInt( val, radix ); + } + else if ( new BigInteger( val, radix ).bitLength() > 32 ) { + throw new NumberFormatException( "integer number too large" ); + } + } + }; + br.validate(); + } + + @Override + public Class getLiteral() { + return int.class; + } + } + + private static class LongAnalyzer implements LiteralAnalyzer { + + @Override + public void validate(String s) { + NumberRepresentation br = new NumberRepresentation( s, true, true, false ) { + + @Override + void parse(String val, int radix) { + if ( radix == 10 ) { + // when decimal: treat like signed + Long.parseLong( val, radix ); + } + else if ( new BigInteger( val, radix ).bitLength() > 64 ) { + throw new NumberFormatException( "integer number too large" ); + } + } + }; + br.validate(); + } + + @Override + public Class getLiteral() { + return long.class; + } + } + + private static class ShortAnalyzer implements LiteralAnalyzer { + + @Override + public void validate(String s) { + NumberRepresentation br = new NumberRepresentation( s, true, false, false ) { + + @Override + void parse(String val, int radix) { + Short.parseShort( val, radix ); + } + }; + br.validate(); + } + + @Override + public Class getLiteral() { + return int.class; + } + } private NativeTypes() { } static { - Map, Class> tmp = new HashMap, Class>(); + Map, Class> tmp = new HashMap<>(); tmp.put( Byte.class, byte.class ); tmp.put( Short.class, short.class ); tmp.put( Integer.class, int.class ); @@ -49,7 +403,7 @@ private NativeTypes() { WRAPPER_TO_PRIMITIVE_TYPES = Collections.unmodifiableMap( tmp ); - tmp = new HashMap, Class>(); + tmp = new HashMap<>(); tmp.put( byte.class, Byte.class ); tmp.put( short.class, Short.class ); tmp.put( int.class, Integer.class ); @@ -60,6 +414,68 @@ private NativeTypes() { tmp.put( char.class, Character.class ); PRIMITIVE_TO_WRAPPER_TYPES = Collections.unmodifiableMap( tmp ); + + NUMBER_TYPES.add( byte.class ); + NUMBER_TYPES.add( short.class ); + NUMBER_TYPES.add( int.class ); + NUMBER_TYPES.add( long.class ); + NUMBER_TYPES.add( float.class ); + NUMBER_TYPES.add( double.class ); + NUMBER_TYPES.add( Byte.class ); + NUMBER_TYPES.add( Short.class ); + NUMBER_TYPES.add( Integer.class ); + NUMBER_TYPES.add( Long.class ); + NUMBER_TYPES.add( Float.class ); + NUMBER_TYPES.add( Double.class ); + NUMBER_TYPES.add( BigInteger.class ); + NUMBER_TYPES.add( BigDecimal.class ); + + Map tmp2 = new HashMap<>(); + tmp2.put( boolean.class.getCanonicalName(), new BooleanAnalyzer() ); + tmp2.put( Boolean.class.getCanonicalName(), new BooleanAnalyzer() ); + tmp2.put( char.class.getCanonicalName(), new CharAnalyzer() ); + tmp2.put( Character.class.getCanonicalName(), new CharAnalyzer() ); + tmp2.put( byte.class.getCanonicalName(), new ByteAnalyzer() ); + tmp2.put( Byte.class.getCanonicalName(), new ByteAnalyzer() ); + tmp2.put( double.class.getCanonicalName(), new DoubleAnalyzer() ); + tmp2.put( Double.class.getCanonicalName(), new DoubleAnalyzer() ); + tmp2.put( float.class.getCanonicalName(), new FloatAnalyzer() ); + tmp2.put( Float.class.getCanonicalName(), new FloatAnalyzer() ); + tmp2.put( int.class.getCanonicalName(), new IntAnalyzer() ); + tmp2.put( Integer.class.getCanonicalName(), new IntAnalyzer() ); + tmp2.put( long.class.getCanonicalName(), new LongAnalyzer() ); + tmp2.put( Long.class.getCanonicalName(), new LongAnalyzer() ); + tmp2.put( short.class.getCanonicalName(), new ShortAnalyzer() ); + tmp2.put( Short.class.getCanonicalName(), new ShortAnalyzer() ); + + ANALYZERS = Collections.unmodifiableMap( tmp2 ); + + TYPE_KIND_NAME.put( TypeKind.BOOLEAN, "boolean" ); + TYPE_KIND_NAME.put( TypeKind.BYTE, "byte" ); + TYPE_KIND_NAME.put( TypeKind.SHORT, "short" ); + TYPE_KIND_NAME.put( TypeKind.INT, "int" ); + TYPE_KIND_NAME.put( TypeKind.LONG, "long" ); + TYPE_KIND_NAME.put( TypeKind.CHAR, "char" ); + TYPE_KIND_NAME.put( TypeKind.FLOAT, "float" ); + TYPE_KIND_NAME.put( TypeKind.DOUBLE, "double" ); + + Map tmp3 = new HashMap<>( ); + tmp3.put( byte.class.getName(), 1 ); + tmp3.put( Byte.class.getName(), 1 ); + tmp3.put( short.class.getName(), 2 ); + tmp3.put( Short.class.getName(), 2 ); + tmp3.put( int.class.getName(), 3 ); + tmp3.put( Integer.class.getName(), 3 ); + tmp3.put( long.class.getName(), 4 ); + tmp3.put( Long.class.getName(), 4 ); + tmp3.put( float.class.getName(), 5 ); + tmp3.put( Float.class.getName(), 5 ); + tmp3.put( double.class.getName(), 6 ); + tmp3.put( Double.class.getName(), 6 ); + tmp3.put( BigInteger.class.getName(), 50 ); + tmp3.put( BigDecimal.class.getName(), 51 ); + tmp3.put( String.class.getName(), 51 ); + NARROWING_LUT = Collections.unmodifiableMap( tmp3 ); } public static Class getWrapperType(Class clazz) { @@ -77,4 +493,59 @@ public static Class getPrimitiveType(Class clazz) { return WRAPPER_TO_PRIMITIVE_TYPES.get( clazz ); } + + public static boolean isNative(String fullyQualifiedName) { + return ANALYZERS.containsKey( fullyQualifiedName ); + } + + public static boolean isNumber(Class clazz) { + if ( clazz == null ) { + return false; + } + else { + return NUMBER_TYPES.contains( clazz ); + } + } + + /** + * + * @param className FQN of the literal native class + * @param literal literal to verify + * @return literal class when the literal is a proper literal for the provided kind. + * @throws IllegalArgumentException when the literal does not match to the provided native type className + */ + public static Class getLiteral(String className, String literal) { + LiteralAnalyzer analyzer = ANALYZERS.get( className ); + Class result = null; + if ( analyzer != null ) { + analyzer.validate( literal ); + result = analyzer.getLiteral(); + } + return result; + } + + /** + * The name that should be used for the {@code typeKind}. + * Should be used in order to get the name of a primitive type + * + * @param typeKind the type kind + * + * @return the name that should be used for the {@code typeKind} + */ + public static String getName(TypeKind typeKind) { + return TYPE_KIND_NAME.get( typeKind ); + } + + public static boolean isNarrowing( String sourceFQN, String targetFQN ) { + + boolean isNarrowing = false; + + Integer sourcePosition = NARROWING_LUT.get( sourceFQN ); + Integer targetPosition = NARROWING_LUT.get( targetFQN ); + + if ( sourcePosition != null && targetPosition != null ) { + isNarrowing = ( targetPosition - sourcePosition < 0 ); + } + return isNarrowing; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Nouns.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Nouns.java index 4da86b7317..ccd7a1e863 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Nouns.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Nouns.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.util; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/NullabilityResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/util/NullabilityResolver.java new file mode 100644 index 0000000000..768886cb95 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/NullabilityResolver.java @@ -0,0 +1,257 @@ +/* + * 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.util; + +import java.util.List; +import java.util.function.BooleanSupplier; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * Resolver for JSpecify nullness annotations on elements, used to decide whether a null + * check is required for a source-to-target property mapping. + *

      + * A single instance is created per annotation-processor run and carries the + * {@link #enabled} flag derived from the {@code mapstruct.disableJSpecify} option. When + * disabled, all public entry points short-circuit to {@link Nullability#UNKNOWN} / + * {@code null}, which causes downstream callers to fall back to the pre-JSpecify + * {@code NullValueCheckStrategy}-based behavior. + * + * @author Filip Hrisafov + */ +public class NullabilityResolver { + + /** + * Represents the effective nullability of a source or target element. + */ + public enum Nullability { + /** + * The element is effectively non-null — either directly annotated {@code @NonNull}, + * or within a {@code @NullMarked} scope without a closer {@code @NullUnmarked} or + * {@code @Nullable} override. + */ + NON_NULL, + /** + * The element is explicitly annotated {@code @Nullable}. + */ + NULLABLE, + /** + * Nullability is unspecified — no annotation applies and the element is not within + * an applicable {@code @NullMarked} scope (or JSpecify support is disabled). + */ + UNKNOWN + } + + private final boolean enabled; + + public NullabilityResolver(boolean enabled) { + this.enabled = enabled; + } + + /** + * Determines the nullability of an accessor element based on JSpecify annotations. + *

      + * For getter methods ({@link ExecutableElement}), this checks the return type's annotations + * since JSpecify annotations are {@code TYPE_USE} annotations placed on the return type. + * For setter parameters ({@link VariableElement}), this checks the parameter type's annotations. + * For fields ({@link VariableElement}), this checks the field type's annotations. + *

      + * If no direct annotation is found, the method walks the enclosing element chain looking + * for a method-level {@code @NullMarked} / {@code @NullUnmarked}. If nothing is found there + * either, {@code enclosingTypeNullMarked} is consulted — when it returns {@code true}, + * unannotated types are effectively {@code @NonNull}. The supplier is invoked at most once. + *

      + * When this resolver is disabled, {@link Nullability#UNKNOWN} is returned without any + * inspection — the supplier is not invoked. + * + * @param element the accessor element to inspect (getter method, setter parameter, or field); + * may be {@code null} in which case {@link Nullability#UNKNOWN} is returned + * @param enclosingTypeNullMarked supplier for whether the enclosing bean type is in a + * {@code @NullMarked} scope; must be non-{@code null} + * @return the nullability state + */ + public Nullability getNullability(Element element, BooleanSupplier enclosingTypeNullMarked) { + if ( !enabled || element == null ) { + return Nullability.UNKNOWN; + } + + // JSpecify annotations are TYPE_USE annotations. + // For getters: annotation is on the return type + // For setter parameters / fields: annotation is on the variable type + if ( element instanceof ExecutableElement ) { + // Getter method — check the return type annotations + TypeMirror returnType = ( (ExecutableElement) element ).getReturnType(); + Nullability result = getNullabilityFromTypeMirror( returnType ); + if ( result != Nullability.UNKNOWN ) { + return result; + } + } + else if ( element instanceof VariableElement ) { + // Setter parameter or field — check the variable type annotations + TypeMirror type = element.asType(); + Nullability result = getNullabilityFromTypeMirror( type ); + if ( result != Nullability.UNKNOWN ) { + return result; + } + } + + // Fallback: check declaration-level annotation mirrors. Some compilers (notably ECJ) + // surface JSpecify TYPE_USE annotations on the element rather than the type mirror. + Nullability fromElement = getNullabilityFromAnnotationMirrors( element.getAnnotationMirrors() ); + if ( fromElement != Nullability.UNKNOWN ) { + return fromElement; + } + + // Walk enclosing elements up to the declaring type to honor method-level + // @NullMarked / @NullUnmarked (e.g. a @NullUnmarked method inside a @NullMarked class + // must revert unannotated types back to unknown nullability). + Boolean elementScope = resolveElementScope( element ); + if ( elementScope != null ) { + return elementScope ? Nullability.NON_NULL : Nullability.UNKNOWN; + } + + // No element-level scope — consult the enclosing bean type's @NullMarked scope. + if ( enclosingTypeNullMarked.getAsBoolean() ) { + return Nullability.NON_NULL; + } + + return Nullability.UNKNOWN; + } + + /** + * Determines the nullability of a write-accessor element — either a setter method or a field. + *

      + * For setter methods ({@link ExecutableElement}) the nullability annotation lives on the + * parameter type, not on the method itself, so the first parameter's nullability is returned. + * For fields (or any other element type) the element's own type nullability is returned. + * + * @param element the write-accessor element (setter or field); may be + * {@code null} in which case {@link Nullability#UNKNOWN} is returned + * @param enclosingTypeNullMarked supplier for whether the enclosing bean type is in a + * {@code @NullMarked} scope; must be non-{@code null} + * @return the nullability state of the setter's parameter or of the field + */ + public Nullability getSetterNullability(Element element, BooleanSupplier enclosingTypeNullMarked) { + if ( !enabled ) { + return Nullability.UNKNOWN; + } + if ( element instanceof ExecutableElement ) { + List parameters = ( (ExecutableElement) element ).getParameters(); + if ( parameters.isEmpty() ) { + // A zero-parameter method is not a valid write accessor. Falling through to + // getNullability would inspect the return type, which is meaningless here. + return Nullability.UNKNOWN; + } + return getNullability( parameters.get( 0 ), enclosingTypeNullMarked ); + } + // Field or other write accessor: consult the element's own type nullability. + return getNullability( element, enclosingTypeNullMarked ); + } + + /** + * Determines whether a null check is required for a property mapping based on JSpecify annotations + * on the source and target elements. + *

      + * Only returns a non-null decision for the clear-cut cases: + * source {@code @NonNull} (skip check) or target {@code @NonNull} (always check). + * All other cases return {@code null} to defer to the existing {@code NullValueCheckStrategy}. + * + * @param sourceNullability the nullability of the source (getter return type / parameter) + * @param targetNullability the nullability of the target (setter parameter / field) + * @return {@code Boolean.TRUE} if a null check is needed, {@code Boolean.FALSE} if it should be skipped, + * or {@code null} if JSpecify annotations are not present and the existing strategy should be used + */ + public Boolean requiresNullCheck(Nullability sourceNullability, Nullability targetNullability) { + if ( !enabled ) { + return null; + } + if ( sourceNullability == Nullability.NON_NULL ) { + // Source is guaranteed non-null, no null check needed + return Boolean.FALSE; + } + if ( targetNullability == Nullability.NON_NULL ) { + // Target requires non-null: always check (regardless of source annotation) + return Boolean.TRUE; + } + // All other cases: defer to existing NullValueCheckStrategy + return null; + } + + /** + * Walks from {@code element} up to (but not including) its declaring {@link TypeElement}, + * checking for {@code @NullMarked} / {@code @NullUnmarked} on intermediate elements + * (e.g. the enclosing method of a parameter, or the element itself for a field / getter). + * + * @return {@code TRUE} when a closer {@code @NullMarked} is found, {@code FALSE} when a + * closer {@code @NullUnmarked} is found, or {@code null} when neither is present before + * the declaring type is reached (leaving the bean-type scope to decide). + */ + private static Boolean resolveElementScope(Element element) { + Element current = element; + while ( current != null && !isTypeElement( current ) ) { + Boolean scope = findScopeAnnotation( current ); + if ( scope != null ) { + return scope; + } + current = current.getEnclosingElement(); + } + return null; + } + + private static boolean isTypeElement(Element element) { + ElementKind kind = element.getKind(); + return kind.isClass() || kind.isInterface(); + } + + private static Boolean findScopeAnnotation(Element element) { + for ( AnnotationMirror mirror : element.getAnnotationMirrors() ) { + Element annotationElement = mirror.getAnnotationType().asElement(); + if ( !( annotationElement instanceof TypeElement ) ) { + continue; + } + String fqn = ( (TypeElement) annotationElement ).getQualifiedName().toString(); + if ( JSpecifyConstants.NULL_MARKED_FQN.equals( fqn ) ) { + return Boolean.TRUE; + } + if ( JSpecifyConstants.NULL_UNMARKED_FQN.equals( fqn ) ) { + return Boolean.FALSE; + } + } + return null; + } + + private static Nullability getNullabilityFromTypeMirror(TypeMirror typeMirror) { + if ( typeMirror == null ) { + return Nullability.UNKNOWN; + } + return getNullabilityFromAnnotationMirrors( typeMirror.getAnnotationMirrors() ); + } + + private static Nullability getNullabilityFromAnnotationMirrors( + List annotationMirrors) { + for ( AnnotationMirror mirror : annotationMirrors ) { + Element annotationElement = mirror.getAnnotationType().asElement(); + if ( !( annotationElement instanceof TypeElement ) ) { + // Defensive: during incremental builds the annotation may be an ErrorType + // whose element is not a TypeElement. Skip instead of failing with a CCE. + continue; + } + String fqn = ( (TypeElement) annotationElement ).getQualifiedName().toString(); + if ( JSpecifyConstants.NON_NULL_FQN.equals( fqn ) ) { + return Nullability.NON_NULL; + } + if ( JSpecifyConstants.NULLABLE_FQN.equals( fqn ) ) { + return Nullability.NULLABLE; + } + } + return Nullability.UNKNOWN; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java new file mode 100644 index 0000000000..76126bf488 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RepeatableAnnotations.java @@ -0,0 +1,126 @@ +/* + * 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.util; + +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.tools.gem.Gem; + +/** + * A base helper class that provides utility methods for working with repeatable annotations. + * + * @param The singular annotation type + * @param The multiple annotation type + * @param The underlying holder for the processed annotations + * + * @author Ben Zegveld + */ +public abstract class RepeatableAnnotations { + private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation"; + private static final String ORG_MAPSTRUCT_PKG = "org.mapstruct"; + + private ElementUtils elementUtils; + private final String singularFqn; + private final String multipleFqn; + + protected RepeatableAnnotations(ElementUtils elementUtils, String singularFqn, String multipleFqn) { + this.elementUtils = elementUtils; + this.singularFqn = singularFqn; + this.multipleFqn = multipleFqn; + } + + /** + * @param element the element on which the Gem needs to be found + * @return the Gem found on the element. + */ + protected abstract SINGULAR singularInstanceOn(Element element); + + /** + * @param element the element on which the Gems needs to be found + * @return the Gems found on the element. + */ + protected abstract MULTIPLE multipleInstanceOn(Element element); + + /** + * @param gem the annotation gem to be processed + * @param source the source element where the request originated from + * @param mappings the collection of completed processing + */ + protected abstract void addInstance(SINGULAR gem, Element source, Set mappings); + + /** + * @param gems the annotation gems to be processed + * @param source the source element where the request originated from + * @param mappings the collection of completed processing + */ + protected abstract void addInstances(MULTIPLE gems, Element source, Set mappings); + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @return The processed annotations for the given element + */ + public Set getProcessedAnnotations(Element source) { + return getMappings( source, source, new LinkedHashSet<>(), new HashSet<>() ); + } + + /** + * Retrieves the processed annotations. + * + * @param source The source element of interest + * @param element Element of interest: method, or (meta) annotation + * @param mappingOptions LinkedSet of mappings found so far + * @param handledElements The collection of already handled elements to handle recursion correctly. + * @return The processed annotations for the given element + */ + private Set getMappings(Element source, Element element, + LinkedHashSet mappingOptions, + Set handledElements) { + + for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) { + Element lElement = annotationMirror.getAnnotationType().asElement(); + if ( isAnnotation( lElement, singularFqn ) ) { + // although getInstanceOn does a search on annotation mirrors, the order is preserved + SINGULAR mapping = singularInstanceOn( element ); + addInstance( mapping, source, mappingOptions ); + } + else if ( isAnnotation( lElement, multipleFqn ) ) { + // although getInstanceOn does a search on annotation mirrors, the order is preserved + MULTIPLE mappings = multipleInstanceOn( element ); + addInstances( mappings, source, mappingOptions ); + } + else if ( !isAnnotationInPackage( lElement, JAVA_LANG_ANNOTATION_PGK ) + && !isAnnotationInPackage( lElement, ORG_MAPSTRUCT_PKG ) + && !handledElements.contains( lElement ) ) { + // recur over annotation mirrors + handledElements.add( lElement ); + getMappings( source, lElement, mappingOptions, handledElements ); + } + } + return mappingOptions; + } + + private boolean isAnnotationInPackage(Element element, String packageFQN) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return packageFQN.equals( elementUtils.getPackageOf( element ).getQualifiedName().toString() ); + } + return false; + } + + private boolean isAnnotation(Element element, String annotationFQN) { + if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) { + return annotationFQN.equals( ( (TypeElement) element ).getQualifiedName().toString() ); + } + return false; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.java b/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.java new file mode 100644 index 0000000000..6106fdc1b0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.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.ap.internal.util; + +import java.util.HashSet; +import java.util.Set; +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +/** + * Keeps contextual data in the scope of one annotation processing round. + * + * @author Gunnar Morling + */ +public class RoundContext { + + private final AnnotationProcessorContext annotationProcessorContext; + private final Set clearedTypes; + + public RoundContext(AnnotationProcessorContext annotationProcessorContext) { + this.annotationProcessorContext = annotationProcessorContext; + this.clearedTypes = new HashSet<>(); + } + + public AnnotationProcessorContext getAnnotationProcessorContext() { + return annotationProcessorContext; + } + + /** + * Marks the given type as being ready for further processing. + * @param type the type that is ready for further processing by MapStruct + */ + public void addTypeReadyForProcessing(TypeMirror type) { + clearedTypes.add( type ); + } + + /** + * Whether the given type has been found to be ready for further processing or not. This is the case if the type's + * hierarchy is complete (no super-types need to be generated by other processors) and no processors have signaled + * the intention to amend the given type. + * + * @param type the typed to be checked for its readiness + * @return true when the type is ready to be processed by MapStruct + * + * @see AstModifyingAnnotationProcessor + */ + public boolean isReadyForProcessing(TypeMirror type) { + return clearedTypes.contains( type ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java new file mode 100644 index 0000000000..292512ff80 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.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.ap.internal.util; + +import java.util.Iterator; +import java.util.ServiceLoader; + +/** + * A simple locator for SPI implementations. + * + * @author Christian Schuster + */ +public class Services { + + private Services() { + } + + public static Iterable all(Class serviceType) { + return ServiceLoader.load( serviceType, Services.class.getClassLoader() ); + } + + public static T get(Class serviceType, T defaultValue) { + + Iterator services = ServiceLoader.load( serviceType, Services.class.getClassLoader() ).iterator(); + + T result; + if ( services.hasNext() ) { + result = services.next(); + } + else { + result = defaultValue; + } + if ( services.hasNext() ) { + throw new IllegalStateException( + "Multiple implementations have been found for the service provider interface" ); + } + return result; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/SpecificCompilerWorkarounds.java b/processor/src/main/java/org/mapstruct/ap/internal/util/SpecificCompilerWorkarounds.java deleted file mode 100644 index e8b86dd20d..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/SpecificCompilerWorkarounds.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.util; - -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -/** - * Contains workarounds for various quirks in specific compilers. - * - * @author Sjaak Derksen - * @author Andreas Gudian - */ -public class SpecificCompilerWorkarounds { - - private SpecificCompilerWorkarounds() { } - - /** - * Tests whether one type is assignable to another. - * - *

      - * Work-around for a bug most likely related to problem solved with {@link #isSubType} - * - * @param types the type utils - * @param t1 the first type - * @param t2 the second type - * @return {@code true} if and only if the first type is assignable to the second - * @throws IllegalArgumentException if given an executable or package type - */ - public static boolean isAssignable(Types types, TypeMirror t1, TypeMirror t2) { - if ( t1.getKind() == TypeKind.VOID ) { - return false; - } - - return types.isAssignable( erasure( types, t1 ), erasure( types, t2 ) ); - } - - /** - * Tests whether one type is a subtype of another. Any type is considered to be a subtype of itself. Also see JLS section 4.10, Subtyping. - *

      - * Work-around for a bug related to sub-typing in the Eclipse JSR 269 implementation. - * - * @param types the type utils - * @param t1 the first type - * @param t2 the second type - * @return {@code true} if and only if the first type is a subtype of the second - * @throws IllegalArgumentException if given an executable or package type - */ - public static boolean isSubType(Types types, TypeMirror t1, TypeMirror t2) { - if ( t1.getKind() == TypeKind.VOID ) { - return false; - } - - return types.isSubtype( erasure( types, t1 ), erasure( types, t2 ) ); - } - - /** - * Returns the erasure of a type. - *

      - * Performs an additional test on the given type to check if it is not void. Calling - * {@link Types#erasure(TypeMirror)} with a void kind type will create a ClassCastException in Eclipse JDT. See the - * JLS, section 4.6 Type Erasure, for reference. - * - * @param types the type utils - * @param t the type to be erased - * @return the erasure of the given type - * @throws IllegalArgumentException if given a package type - */ - public static TypeMirror erasure(Types types, TypeMirror t) { - if ( t.getKind() == TypeKind.VOID || t.getKind() == TypeKind.NULL ) { - return t; - } - else { - return types.erasure( t ); - } - } - - /** - * When running during Eclipse Incremental Compilation, we might get a TypeElement that has an UnresolvedTypeBinding - * and which is not automatically resolved. In that case, getEnclosedElements returns an empty list. We take that as - * a hint to check if the TypeElement resolved by FQN might have any enclosed elements and, if so, return the - * resolved element. - * - * @param elementUtils element utils - * @param element the original element - * @return the element freshly resolved using the qualified name, if the original element did not return any - * enclosed elements, whereas the resolved element does return enclosed elements. - */ - public static TypeElement replaceTypeElementIfNecessary(Elements elementUtils, TypeElement element) { - if ( element.getEnclosedElements().isEmpty() ) { - TypeElement resolvedByName = elementUtils.getTypeElement( element.getQualifiedName() ); - if ( resolvedByName != null && !resolvedByName.getEnclosedElements().isEmpty() ) { - return resolvedByName; - } - } - return element; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java index 6288bbc9f3..61ca397b8d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Strings.java @@ -1,26 +1,14 @@ -/** - * Copyright 2012-2015 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.util; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.Locale; import java.util.Set; import static org.mapstruct.ap.internal.util.Collections.asSet; @@ -85,22 +73,28 @@ public class Strings { "while" ); + private static final char UNDERSCORE = '_'; + private Strings() { } public static String capitalize(String string) { - return string == null ? null : string.substring( 0, 1 ).toUpperCase() + string.substring( 1 ); + return string == null ? null : string.substring( 0, 1 ).toUpperCase( Locale.ROOT ) + string.substring( 1 ); } public static String decapitalize(String string) { - return string == null ? null : string.substring( 0, 1 ).toLowerCase() + string.substring( 1 ); + return string == null ? null : string.substring( 0, 1 ).toLowerCase( Locale.ROOT ) + string.substring( 1 ); } public static String join(Iterable iterable, String separator) { + return join( iterable, separator, null ); + } + + public static String join(Iterable iterable, String separator, Extractor extractor) { StringBuilder sb = new StringBuilder(); boolean isFirst = true; - for ( Object object : iterable ) { + for ( T object : iterable ) { if ( !isFirst ) { sb.append( separator ); } @@ -108,7 +102,7 @@ public static String join(Iterable iterable, String separator) { isFirst = false; } - sb.append( object ); + sb.append( extractor == null ? object : extractor.apply( object ) ); } return sb.toString(); @@ -135,8 +129,12 @@ public static boolean isEmpty(String string) { return string == null || string.isEmpty(); } - public static String getSaveVariableName(String name, String... existingVariableNames) { - return getSaveVariableName( name, Arrays.asList( existingVariableNames ) ); + public static boolean isNotEmpty(String string) { + return !isEmpty( string ); + } + + public static String getSafeVariableName(String name, String... existingVariableNames) { + return getSafeVariableName( name, Arrays.asList( existingVariableNames ) ); } /** @@ -149,17 +147,127 @@ public static String getSaveVariableName(String name, String... existingVariable * @return a variable name based on the given original name, not conflicting with any of the given other names or * any Java keyword; starting with a lower-case letter */ - public static String getSaveVariableName(String name, Collection existingVariableNames) { - name = decapitalize( name ); + public static String getSafeVariableName(String name, Collection existingVariableNames) { + name = decapitalize( sanitizeIdentifierName( name ) ); + name = joinAndCamelize( extractParts( name ) ); - Set conflictingNames = new HashSet( KEYWORDS ); + Set conflictingNames = new HashSet<>( KEYWORDS ); conflictingNames.addAll( existingVariableNames ); - while ( conflictingNames.contains( name ) ) { - name = name + "_"; + if ( !conflictingNames.contains( name ) ) { + return name; + } + + int c = 1; + String separator = Character.isDigit( name.charAt( name.length() - 1 ) ) ? "_" : ""; + while ( conflictingNames.contains( name + separator + c ) ) { + c++; + } + + return name + separator + c; + } + + /** + * @param identifier identifier to sanitize + * @return the identifier without any characters that are not allowed as part of a Java identifier. + */ + public static String sanitizeIdentifierName(String identifier) { + if ( identifier != null && identifier.length() > 0 ) { + + int firstAlphabeticIndex = 0; + while ( firstAlphabeticIndex < identifier.length() && + ( identifier.charAt( firstAlphabeticIndex ) == UNDERSCORE || + Character.isDigit( identifier.charAt( firstAlphabeticIndex ) ) ) ) { + firstAlphabeticIndex++; + } + + if ( firstAlphabeticIndex < identifier.length() ) { + // If it is not consisted of only underscores + String firstAlphaString = identifier.substring( firstAlphabeticIndex ).replace( "[]", "Array" ); + + StringBuilder sb = new StringBuilder( firstAlphaString.length() ); + for ( int i = 0; i < firstAlphaString.length(); i++ ) { + int codePoint = firstAlphaString.codePointAt( i ); + if ( Character.isJavaIdentifierPart( codePoint ) || codePoint == '.' ) { + sb.appendCodePoint( codePoint ); + } + else { + sb.append( '_' ); + } + } + return sb.toString(); + } + + return identifier.replace( "[]", "Array" ); } + return identifier; + } + + /** + * Returns a stub property name from full class name by stripping away the package and decapitalizing the name + * For example will return {@code fooBar} for {@code com.foo.bar.baz.FooBar} class name + * + * @param fullyQualifiedName fully qualified class name, such as com.foo.bar.baz.FooBar + * @return stup property name, such as fooBar + */ + public static String stubPropertyName(String fullyQualifiedName) { + return Strings.decapitalize( fullyQualifiedName.substring( fullyQualifiedName.lastIndexOf( '.' ) + 1 ) ); + } - return name; + /** + * It removes the dots from the name and creates an {@link Iterable} from them. + * + * E.q. for the name {@code props.font} it will return an {@link Iterable} containing the {@code props} and + * {@code font} + * @param name the name that needs to be parsed into parts + * @return an {@link Iterable} containing all the parts of the name. + */ + static Iterable extractParts(String name) { + return Arrays.asList( name.split( "\\." ) ); } + public static String getMostSimilarWord(String word, Collection similarWords) { + int minLevenstein = Integer.MAX_VALUE; + String mostSimilarWord = null; + for ( String similarWord : similarWords ) { + int levensteinDistance = levenshteinDistance( similarWord, word ); + if ( levensteinDistance < minLevenstein ) { + minLevenstein = levensteinDistance; + mostSimilarWord = similarWord; + } + } + return mostSimilarWord; + } + + private static int levenshteinDistance(String s, String t) { + int sLength = s.length() + 1; + int tLength = t.length() + 1; + + int[][] distances = new int[tLength][]; + for ( int i = 0; i < tLength; i++ ) { + distances[i] = new int[sLength]; + } + + for ( int i = 0; i < tLength; i++ ) { + distances[i][0] = i; + } + for ( int i = 0; i < sLength; i++ ) { + distances[0][i] = i; + } + + for ( int i = 1; i < tLength; i++ ) { + for ( int j = 1; j < sLength; j++ ) { + int cost = s.charAt( j - 1 ) == t.charAt( i - 1 ) ? 0 : 1; + distances[i][j] = Math.min( + Math.min( + distances[i - 1][j] + 1, + distances[i][j - 1] + 1 + ), + distances[i - 1][j - 1] + cost + ); + } + } + + return distances[tLength - 1][sLength - 1]; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/TypeUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/TypeUtils.java new file mode 100644 index 0000000000..926cd5eef8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/TypeUtils.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.ap.internal.util; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Types; + +import org.mapstruct.ap.internal.version.VersionInformation; + +public interface TypeUtils extends Types { + + static TypeUtils create(ProcessingEnvironment processingEnvironment, VersionInformation info ) { + if ( info.isEclipseJDTCompiler() ) { + return new EclipseTypeUtilsDecorator( processingEnvironment ); + } + else { + return new JavacTypeUtilsDecorator( processingEnvironment ); + } + } + + boolean isSubtypeErased(TypeMirror t1, TypeMirror t2); +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/XmlConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/XmlConstants.java new file mode 100644 index 0000000000..532b9f0bce --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/XmlConstants.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.ap.internal.util; + +/** + * Helper holding JAXB time full qualified class names for conversion registration + * + * @author Filip Hrisafov + */ +public final class XmlConstants { + + // CHECKSTYLE:OFF + public static final String JAVAX_XML_XML_GREGORIAN_CALENDAR = "javax.xml.datatype.XMLGregorianCalendar"; + public static final String JAVAX_XML_DATATYPE_CONFIGURATION_EXCEPTION = "javax.xml.datatype.DatatypeConfigurationException"; + public static final String JAVAX_XML_DATATYPE_FACTORY = "javax.xml.datatype.DatatypeFactory"; + public static final String JAVAX_XML_DATATYPE_CONSTANTS = "javax.xml.datatype.DatatypeConstants"; + // CHECKSTYLE:ON + + private XmlConstants() { + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/Accessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/Accessor.java new file mode 100644 index 0000000000..d624b7788a --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/Accessor.java @@ -0,0 +1,51 @@ +/* + * 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.util.accessor; + +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * This represents an Accessor that can be used for writing/reading a property to/from a bean. + * + * @author Filip Hrisafov + */ +public interface Accessor { + + /** + * This returns the type that this accessor gives as a return. + * + * e.g. The {@link ExecutableElement#getReturnType()} if this is a method accessor, + * or {@link VariableElement#asType()} for field accessors. + * + * @return the type that the accessor gives as a return + */ + TypeMirror getAccessedType(); + + /** + * @return the simple name of the accessor + */ + String getSimpleName(); + + /** + * @return the set of modifiers that the accessor has + */ + Set getModifiers(); + + /** + * @return the underlying {@link Element}, {@link VariableElement} or {@link ExecutableElement} + */ + Element getElement(); + + /** + * @return type of the accessor + */ + AccessorType getAccessorType(); +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.java new file mode 100644 index 0000000000..112c1c512d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AccessorType.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.ap.internal.util.accessor; + +public enum AccessorType { + + PARAMETER, + FIELD, + GETTER, + SETTER, + ADDER; + + public boolean isFieldAssignment() { + return this == FIELD || this == PARAMETER; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.java new file mode 100644 index 0000000000..5ebc835ba1 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/DelegateAccessor.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.ap.internal.util.accessor; + +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; + +/** + * An {@link Accessor} which delegates all calls to another {@link Accessor}. + * + * @author Filip Hrisafov + */ +public abstract class DelegateAccessor implements Accessor { + + protected final Accessor delegate; + + protected DelegateAccessor(Accessor delegate) { + this.delegate = delegate; + } + + @Override + public TypeMirror getAccessedType() { + return delegate.getAccessedType(); + } + + @Override + public String getSimpleName() { + return delegate.getSimpleName(); + } + + @Override + public Set getModifiers() { + return delegate.getModifiers(); + } + + @Override + public Element getElement() { + return delegate.getElement(); + } + + @Override + public AccessorType getAccessorType() { + return delegate.getAccessorType(); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java new file mode 100644 index 0000000000..4b363815b7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ElementAccessor.java @@ -0,0 +1,75 @@ +/* + * 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.util.accessor; + +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * An {@link Accessor} that wraps a {@link Element}. + * Used for getter, setter, filed, constructor, record-class, etc. + * @author Filip Hrisafov + * @author Tang Yang + */ +public class ElementAccessor implements Accessor { + + private final Element element; + private final String name; + private final AccessorType accessorType; + private final TypeMirror accessedType; + + public ElementAccessor(VariableElement variableElement, TypeMirror accessedType) { + this( variableElement, accessedType, AccessorType.FIELD ); + } + + public ElementAccessor(Element element, TypeMirror accessedType, String name) { + this.element = element; + this.name = name; + this.accessedType = accessedType; + this.accessorType = AccessorType.PARAMETER; + } + + public ElementAccessor(Element element, TypeMirror accessedType, AccessorType accessorType) { + this.element = element; + this.accessedType = accessedType; + this.accessorType = accessorType; + this.name = null; + } + + @Override + public TypeMirror getAccessedType() { + return accessedType != null ? accessedType : element.asType(); + } + + @Override + public String getSimpleName() { + return name != null ? name : element.getSimpleName().toString(); + } + + @Override + public Set getModifiers() { + return element.getModifiers(); + } + + @Override + public Element getElement() { + return element; + } + + @Override + public String toString() { + return element.toString(); + } + + @Override + public AccessorType getAccessorType() { + return accessorType; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.java new file mode 100644 index 0000000000..7a708995cd --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/MapValueAccessor.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.ap.internal.util.accessor; + +import java.util.Collections; +import java.util.Set; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.type.TypeMirror; + +/** + * An {@link Accessor} that wraps a Map value. + * + * @author Christian Kosmowski + */ +public class MapValueAccessor implements ReadAccessor { + + private final TypeMirror valueTypeMirror; + private final String simpleName; + private final Element element; + + public MapValueAccessor(Element element, TypeMirror valueTypeMirror, String simpleName) { + this.element = element; + this.valueTypeMirror = valueTypeMirror; + this.simpleName = simpleName; + } + + @Override + public TypeMirror getAccessedType() { + return valueTypeMirror; + } + + @Override + public String getSimpleName() { + return this.simpleName; + } + + @Override + public Set getModifiers() { + return Collections.emptySet(); + } + + @Override + public Element getElement() { + return this.element; + } + + @Override + public AccessorType getAccessorType() { + return AccessorType.GETTER; + } + + @Override + public String getReadValueSource() { + return "get( \"" + getSimpleName() + "\" )"; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.java new file mode 100644 index 0000000000..d96788b7f7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/PresenceCheckAccessor.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.ap.internal.util.accessor; + +import javax.lang.model.element.ExecutableElement; + +/** + * Accessor for presence checks. + * + * @author Filip Hrisafov + */ +public interface PresenceCheckAccessor { + + String getPresenceCheckSuffix(); + + static PresenceCheckAccessor methodInvocation(ExecutableElement element) { + return suffix( "." + element.getSimpleName() + "()" ); + } + + static PresenceCheckAccessor mapContainsKey(String propertyName) { + return suffix( ".containsKey( \"" + propertyName + "\" )" ); + } + + static PresenceCheckAccessor suffix(String suffix) { + return () -> suffix; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java new file mode 100644 index 0000000000..4be3c26ff9 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadAccessor.java @@ -0,0 +1,48 @@ +/* + * 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.util.accessor; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; + +/** + * An {@link Accessor} that can be used for reading a property from a bean. + * + * @author Filip Hrisafov + */ +public interface ReadAccessor extends Accessor { + + String getReadValueSource(); + + static ReadAccessor fromField(VariableElement variableElement, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( variableElement, accessedType ) ) { + @Override + public String getReadValueSource() { + return getSimpleName(); + } + }; + } + + static ReadAccessor fromRecordComponent(Element element, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) { + @Override + public String getReadValueSource() { + return getSimpleName() + "()"; + } + }; + } + + static ReadAccessor fromGetter(ExecutableElement element, TypeMirror accessedType) { + return new ReadDelegateAccessor( new ElementAccessor( element, accessedType, AccessorType.GETTER ) ) { + @Override + public String getReadValueSource() { + return getSimpleName() + "()"; + } + }; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.java new file mode 100644 index 0000000000..aa93936662 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ReadDelegateAccessor.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.ap.internal.util.accessor; + +/** + * {@link ReadAccessor} that delegates to another {@link Accessor} and requires an implementation of + * {@link #getSimpleName()} + * + * @author Filip Hrisafov + */ +public abstract class ReadDelegateAccessor extends DelegateAccessor implements ReadAccessor { + + protected ReadDelegateAccessor(Accessor delegate) { + super( delegate ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.java new file mode 100644 index 0000000000..66a6fafc02 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/kotlin/KotlinMetadata.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.ap.internal.util.kotlin; + +import java.util.List; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.type.TypeMirror; + +/** + * Information about a type in case it's a Kotlin type. + * + * @author Filip Hrisafov + */ +public interface KotlinMetadata { + + boolean isDataClass(); + + boolean isSealedClass(); + + ExecutableElement determinePrimaryConstructor(List constructors); + + List getPermittedSubclasses(); +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/util/package-info.java index 8838e9aa1a..5dbc4e0853 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java index 4d6ff6858c..a30a383c0c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/version/VersionInformation.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.version; @@ -25,7 +12,22 @@ */ public interface VersionInformation { String getRuntimeVersion(); + String getRuntimeVendor(); + String getMapStructVersion(); + String getCompiler(); + + boolean isSourceVersionAtLeast9(); + + boolean isSourceVersionAtLeast11(); + + boolean isSourceVersionAtLeast14(); + + boolean isSourceVersionAtLeast19(); + + boolean isEclipseJDTCompiler(); + + boolean isJavacCompiler(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/version/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/version/package-info.java index 7d0d25f001..114194190b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/version/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/version/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerModelElementWriter.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerModelElementWriter.java index d0705b36b3..d3991ce1da 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerModelElementWriter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerModelElementWriter.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.writer; @@ -29,7 +16,6 @@ import freemarker.template.TemplateHashModel; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; - import org.mapstruct.ap.internal.writer.Writable.Context; /** @@ -59,7 +45,7 @@ private static class ExternalParamsTemplateModel implements TemplateHashModel { private final BeanModel object; private final SimpleMapModel extParams; - public ExternalParamsTemplateModel(BeanModel object, SimpleMapModel extParams) { + ExternalParamsTemplateModel(BeanModel object, SimpleMapModel extParams) { this.object = object; this.extParams = extParams; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerWritable.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerWritable.java index d0ad9a1675..e8aa8fcf3a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerWritable.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/FreeMarkerWritable.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.writer; @@ -48,6 +35,8 @@ protected String getTemplateName() { * the class name of the given model element type, appended with the extension {@code *.ftl} is used as template * file name. * + * @param clazz class to obtain a template for + * * @return the name of the template. Must not be {@code null}. */ protected String getTemplateNameForClass(Class clazz) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/IndentationCorrectingWriter.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/IndentationCorrectingWriter.java index b2b71a8452..25cea82c8c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/IndentationCorrectingWriter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/IndentationCorrectingWriter.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.writer; @@ -50,7 +37,7 @@ class IndentationCorrectingWriter extends Writer { * Set to true to enable output of written characters on the console. */ private static final boolean DEBUG = false; - private static final String LINE_SEPARATOR = System.getProperty( "line.separator" ); + private static final String LINE_SEPARATOR = System.lineSeparator( ); private static final boolean IS_WINDOWS = System.getProperty( "os.name" ).startsWith( "Windows" ); private State currentState = State.START_OF_LINE; @@ -117,11 +104,11 @@ State doHandleCharacter(char c, StateContext context) { switch ( c ) { case '{': case '(': - context.indentationLevel++; + context.incrementIndentationLevel(); return IN_TEXT; case '}': case ')': - context.indentationLevel--; + context.decrementIndentationLevel(); return IN_TEXT; case '\"': return IN_STRING; @@ -139,10 +126,11 @@ State doHandleCharacter(char c, StateContext context) { */ @Override void doOnEntry(StateContext context) throws IOException { - context.writer.write( getIndentation( context.indentationLevel ) ); + context.writer.write( getIndentation( context.getIndentationLevel() ) ); if ( DEBUG ) { - System.out.print( new String( getIndentation( context.indentationLevel ) ).replace( " ", "_" ) ); + System.out.print( new String( getIndentation( context.getIndentationLevel() ) ) + .replace( " ", "_" ) ); } } @@ -173,11 +161,11 @@ State doHandleCharacter(char c, StateContext context) { switch ( c ) { case '{': case '(': - context.indentationLevel++; + context.incrementIndentationLevel(); return IN_TEXT; case '}': case ')': - context.indentationLevel--; + context.decrementIndentationLevel(); return IN_TEXT; case '\"': return IN_STRING; @@ -298,14 +286,14 @@ State doHandleCharacter(char c, StateContext context) { switch ( c ) { case '{': case '(': - context.indentationLevel++; + context.incrementIndentationLevel(); return START_OF_LINE; case '}': if ( context.consecutiveLineBreaks > 0 ) { context.consecutiveLineBreaks = 0; // remove previous blank lines } case ')': - context.indentationLevel--; + context.decrementIndentationLevel(); return START_OF_LINE; case '\r': return isWindows() ? IN_LINE_BREAK : AFTER_LINE_BREAK; @@ -407,7 +395,7 @@ private static class StateContext { /** * Keeps track of the current indentation level, as implied by brace characters. */ - int indentationLevel; + private int indentationLevel; /** * The number of consecutive line-breaks when within {@link State#AFTER_LINE_BREAK}. @@ -423,5 +411,22 @@ void reset(char[] characters, int off) { this.lastStateChange = off; this.currentIndex = 0; } + + void incrementIndentationLevel() { + indentationLevel++; + } + + void decrementIndentationLevel() { + // decrementing below 0 indicates misbalanced braces in the code, typically because too many closing braces + // are given in an expression; we let that code pass through, the compiler will complain eventually about + // the malformed source file + if ( indentationLevel > 0 ) { + indentationLevel--; + } + } + + int getIndentationLevel() { + return indentationLevel; + } } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/ModelIncludeDirective.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/ModelIncludeDirective.java index 6e5f07f9b7..ea2448ca59 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/ModelIncludeDirective.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/ModelIncludeDirective.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.writer; @@ -29,7 +16,6 @@ import freemarker.template.TemplateDirectiveModel; import freemarker.template.TemplateException; import freemarker.template.TemplateModel; - import org.mapstruct.ap.internal.writer.ModelWriter.DefaultModelElementWriterContext; /** @@ -61,15 +47,9 @@ public void execute(Environment env, @SuppressWarnings("rawtypes") Map params, T modelElement.write( context, env.getOut() ); } } - catch ( TemplateException te ) { + catch ( TemplateException | RuntimeException | IOException te ) { throw te; } - catch ( IOException ioe ) { - throw ioe; - } - catch ( RuntimeException re ) { - throw re; - } catch ( Exception e ) { throw new RuntimeException( e ); } @@ -110,7 +90,7 @@ private DefaultModelElementWriterContext createContext(Map params) { Map ext = new HashMap( params ); ext.remove( "object" ); - Map, Object> values = new HashMap, Object>(); + Map, Object> values = new HashMap<>(); values.put( Configuration.class, configuration ); values.put( Map.class, ext ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/ModelWriter.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/ModelWriter.java index 28f2d3082d..aeb2593e86 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/ModelWriter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/ModelWriter.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.writer; @@ -25,19 +12,17 @@ import java.io.Reader; import java.net.URL; import java.net.URLConnection; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; - import javax.tools.FileObject; -import org.mapstruct.ap.internal.writer.Writable.Context; - import freemarker.cache.StrongCacheStorage; import freemarker.cache.TemplateLoader; import freemarker.log.Logger; import freemarker.template.Configuration; import freemarker.template.DefaultObjectWrapper; +import org.mapstruct.ap.internal.writer.Writable.Context; /** * Writes Java source files based on given mapper models, using a FreeMarker @@ -75,16 +60,13 @@ public class ModelWriter { } public void writeModel(FileObject sourceFile, Writable model) { - try { - BufferedWriter writer = new BufferedWriter( new IndentationCorrectingWriter( sourceFile.openWriter() ) ); + try ( BufferedWriter writer = new BufferedWriter( new IndentationCorrectingWriter( sourceFile.openWriter() ))) { + Map, Object> values = new HashMap<>(); + values.put( Configuration.class, CONFIGURATION ); - Map, Object> values = new HashMap, Object>(); - values.put( Configuration.class, CONFIGURATION ); + model.write( new DefaultModelElementWriterContext( values ), writer ); - model.write( new DefaultModelElementWriterContext( values ), writer ); - - writer.flush(); - writer.close(); + writer.flush(); } catch ( RuntimeException e ) { throw e; @@ -103,7 +85,9 @@ private static final class SimpleClasspathLoader implements TemplateLoader { @Override public Reader getReader(Object name, String encoding) throws IOException { URL url = getClass().getClassLoader().getResource( String.valueOf( name ) ); - + if ( url == null ) { + throw new IllegalStateException( name + " not found on classpath" ); + } URLConnection connection = url.openConnection(); // don't use jar-file caching, as it caused occasionally closed input streams [at least under JDK 1.8.0_25] @@ -111,7 +95,7 @@ public Reader getReader(Object name, String encoding) throws IOException { InputStream is = connection.getInputStream(); - return new InputStreamReader( is, Charset.forName( "UTF-8" ) ); + return new InputStreamReader( is, StandardCharsets.UTF_8 ); } @Override @@ -139,7 +123,7 @@ static class DefaultModelElementWriterContext implements Context { private final Map, Object> values; DefaultModelElementWriterContext(Map, Object> values) { - this.values = new HashMap, Object>( values ); + this.values = new HashMap<>( values ); } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/Writable.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/Writable.java index 4b771ae2aa..42724b43c9 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/Writable.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/Writable.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.writer; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/writer/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/writer/package-info.java index bd017a8429..36b72bc30b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/writer/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/writer/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/package-info.java b/processor/src/main/java/org/mapstruct/ap/package-info.java index 66c2230fe2..037310099c 100644 --- a/processor/src/main/java/org/mapstruct/ap/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java index d6352a5cad..b4dde64324 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.spi; @@ -28,8 +15,23 @@ */ public interface AccessorNamingStrategy { + /** + * Initializes the accessor naming strategy with the MapStruct processing environment. + * + * @param processingEnvironment environment for facilities + * + * @since 1.3 + */ + default void init(MapStructProcessingEnvironment processingEnvironment) { + + } + /** * Returns the type of the given method. + * + * @param method to be analyzed. + * + * @return the method type. */ MethodType getMethodType(ExecutableElement method); @@ -38,6 +40,10 @@ public interface AccessorNamingStrategy { *

      * The default implementation will e.g. return "name" for {@code public String getName()} or {@code public void * setName(String name)}. + * + * @param getterOrSetterMethod to be analyzed. + * + * @return property name derived from the getterOrSetterMethod */ String getPropertyName(ExecutableElement getterOrSetterMethod); @@ -45,13 +51,27 @@ public interface AccessorNamingStrategy { * Returns the element name of the given adder method. *

      * The default implementation will e.g. return "item" for {@code public void addItem(String item)}. + * + * @param adderMethod to be getterOrSetterMethod. + * + * @return getter name for collections */ String getElementName(ExecutableElement adderMethod); + /** * Returns the getter name of the given collection property. *

      * The default implementation will e.g. return "getItems" for "items". + * + * @param property to be getterOrSetterMethod. + * + * @return getter name for collection properties + * + * @deprecated MapStruct will not call this method anymore. Use {@link #getMethodType(ExecutableElement)} to + * determine the {@link MethodType}. When collections somehow need to be treated special, it should be done in + * {@link #getMethodType(ExecutableElement) } as well. In the future, this method will be removed. */ + @Deprecated String getCollectionGetterName(String property); } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.java new file mode 100644 index 0000000000..3638f70320 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/AdditionalSupportedOptionsProvider.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.ap.spi; + +import java.util.Set; + +/** + * Provider for any additional supported options required for custom SPI implementations. + * The resolved values are retrieved from {@link MapStructProcessingEnvironment#getOptions()}. + */ +public interface AdditionalSupportedOptionsProvider { + + /** + * Returns the supported options required for custom SPI implementations. + * + * @return the additional supported options. + */ + Set getAdditionalSupportedOptions(); + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java b/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java new file mode 100644 index 0000000000..6b9e079523 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.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.spi; + +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.util.Experimental; + +/** + * A contract to be implemented by other annotation processors which - against the design philosophy of JSR 269 - alter + * the types under compilation. + *

      + * This contract will be queried by MapStruct when examining types referenced by mappers to be generated, most notably + * the source and target types of mapping methods. If at least one AST-modifying processor announces further changes to + * such type, the generation of the affected mapper(s) will be deferred to a future round in the annotation processing + * cycle. + *

      + * Implementations are discovered via the service loader, i.e. a JAR providing an AST-modifying processor needs to + * declare its implementation in a file {@code META-INF/services/org.mapstruct.ap.spi.AstModifyingAnnotationProcessor}. + * + * @author Gunnar Morling + */ +@Experimental( "This interface may change in future revisions" ) +public interface AstModifyingAnnotationProcessor { + + /** + * Whether the specified type has been fully processed by this processor or not (i.e. this processor will amend the + * given type's structure after this invocation). + * + * @param type The type of interest + * @return {@code true} if this processor has fully processed the given type (or has no interest in processing this + * type altogether), {@code false} otherwise. + */ + boolean isTypeComplete(TypeMirror type); +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java b/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java new file mode 100644 index 0000000000..5e37987f75 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.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.ap.spi; + +import java.util.Collection; +import javax.lang.model.element.ExecutableElement; + +/** + * Holder for the builder information. + * + * @author Filip Hrisafov + * + * @since 1.3 + */ +public class BuilderInfo { + + private final ExecutableElement builderCreationMethod; + private final Collection buildMethods; + + private BuilderInfo(ExecutableElement builderCreationMethod, Collection buildMethods) { + this.builderCreationMethod = builderCreationMethod; + this.buildMethods = buildMethods; + } + + /** + * The method that can be used for instantiating a builder. This can be: + *

        + *
      • A {@code public static} method in the type being build
      • + *
      • A {@code public static} method in the builder itself
      • + *
      • The default constructor of the builder
      • + *
      + * + * @return the creation method for the builder + */ + public ExecutableElement getBuilderCreationMethod() { + return builderCreationMethod; + } + + /** + * The methods that can be used to build the type being built. + * This should be {@code public} methods within the builder itself + * + * @return the build method for the type + */ + public Collection getBuildMethods() { + return buildMethods; + } + + public static class Builder { + private ExecutableElement builderCreationMethod; + private Collection buildMethods; + + /** + * @param method The creation method for the builder + * + * @return the builder for chaining + * + * @see BuilderInfo#getBuilderCreationMethod() + */ + public Builder builderCreationMethod(ExecutableElement method) { + this.builderCreationMethod = method; + return this; + } + + /** + * @param methods the build methods for the type + * + * @return the builder for chaining + * + * @see BuilderInfo#getBuildMethods() + */ + public Builder buildMethod(Collection methods) { + this.buildMethods = methods; + return this; + } + + /** + * Create the {@link BuilderInfo}. + * + * @return the created {@link BuilderInfo} + * + * @throws IllegalArgumentException if the builder creation or build methods are {@code null} + */ + public BuilderInfo build() { + if ( builderCreationMethod == null ) { + throw new IllegalArgumentException( "Builder creation method is mandatory" ); + } + else if ( buildMethods == null ) { + throw new IllegalArgumentException( "Build methods are mandatory" ); + } + else if ( buildMethods.isEmpty() ) { + throw new IllegalArgumentException( "Build methods must not be empty" ); + } + return new BuilderInfo( builderCreationMethod, buildMethods ); + } + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/BuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/BuilderProvider.java new file mode 100644 index 0000000000..419c85a39d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/BuilderProvider.java @@ -0,0 +1,40 @@ +/* + * 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.spi; + +import javax.lang.model.type.TypeMirror; + +/** + * A service provider interface that is used to detect types that require a builder for mapping. This interface could + * support automatic detection of builders for projects like Lombok, Immutables, AutoValue, etc. + * @author Filip Hrisafov + * + * @since 1.3 + */ +public interface BuilderProvider { + + /** + * Initializes the builder provider with the MapStruct processing environment. + * + * @param processingEnvironment environment for facilities + */ + default void init(MapStructProcessingEnvironment processingEnvironment) { + + } + + /** + * Find the builder information, if any, for the {@code type}. + * + * @param type the type for which a builder should be found + * @return the builder info for the {@code type} if it exists, or {@code null} if there is no builder + * + * @throws TypeHierarchyErroneousException if the type that needs to be visited is not ready yet, this signals the + * MapStruct processor to postpone the generation of the mappers to the next round + * @throws MoreThanOneBuilderCreationMethodException if {@code type} has more than one method that can create the + * builder + */ + BuilderInfo findBuilderInfo(TypeMirror type); +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/CaseEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/CaseEnumTransformationStrategy.java new file mode 100644 index 0000000000..3a4ba18ade --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/CaseEnumTransformationStrategy.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.ap.spi; + +import java.util.Arrays; +import java.util.Locale; +import java.util.stream.Collectors; + +/** + * Applies case transformation to the source enum + * + * @author jpbassinello + * @since 1.5 + */ +public class CaseEnumTransformationStrategy implements EnumTransformationStrategy { + + private static final String UPPER = "upper"; + private static final String LOWER = "lower"; + private static final String CAPITAL = "capital"; + private static final String CASE_ENUM_TRANSFORMATION_STRATEGIES = UPPER + ", " + LOWER + ", " + CAPITAL; + + @Override + public String getStrategyName() { + return "case"; + } + + @Override + public String transform(String value, String configuration) { + switch ( configuration.toLowerCase() ) { + case UPPER: + return value.toUpperCase( Locale.ROOT ); + case LOWER: + return value.toLowerCase( Locale.ROOT ); + case CAPITAL: + return capitalize( value ); + default: + throw new IllegalArgumentException( + "Unexpected configuration for enum case transformation: " + configuration + + ". Allowed values: " + CASE_ENUM_TRANSFORMATION_STRATEGIES); + } + } + + private static String capitalize(String value) { + return Arrays.stream( value.split( "_" ) ) + .map( CaseEnumTransformationStrategy::upperCaseFirst ) + .collect( Collectors.joining( "_" ) ); + } + + private static String upperCaseFirst(String value) { + char[] array = value.toCharArray(); + array[0] = Character.toUpperCase( array[0] ); + for ( int i = 1; i < array.length; i++ ) { + array[i] = Character.toLowerCase( array[i] ); + } + return new String( array ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java new file mode 100644 index 0000000000..20cb1c76c1 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -0,0 +1,307 @@ +/* + * 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.spi; + +import java.util.regex.Pattern; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; +import javax.lang.model.util.Types; + +import kotlin.Metadata; +import kotlin.metadata.Attributes; +import kotlin.metadata.jvm.KotlinClassMetadata; +import org.mapstruct.ap.spi.util.IntrospectorUtils; + +/** + * The default JavaBeans-compliant implementation of the {@link AccessorNamingStrategy} service provider interface. + * + * @author Christian Schuster, Sjaak Derken + */ +public class DefaultAccessorNamingStrategy implements AccessorNamingStrategy { + + private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); + + private static final boolean KOTLIN_METADATA_JVM_PRESENT; + + static { + boolean kotlinMetadataJvmPresent; + try { + Class.forName( + "kotlin.metadata.jvm.KotlinClassMetadata", + false, + AccessorNamingStrategy.class.getClassLoader() + ); + kotlinMetadataJvmPresent = true; + } + catch ( ClassNotFoundException e ) { + kotlinMetadataJvmPresent = false; + } + KOTLIN_METADATA_JVM_PRESENT = kotlinMetadataJvmPresent; + } + + protected Elements elementUtils; + protected Types typeUtils; + + @Override + public void init(MapStructProcessingEnvironment processingEnvironment) { + this.elementUtils = processingEnvironment.getElementUtils(); + this.typeUtils = processingEnvironment.getTypeUtils(); + } + + @Override + public MethodType getMethodType(ExecutableElement method) { + if ( isGetterMethod( method ) ) { + return MethodType.GETTER; + } + else if ( isSetterMethod( method ) ) { + return MethodType.SETTER; + } + else if ( isAdderMethod( method ) ) { + return MethodType.ADDER; + } + else if ( isPresenceCheckMethod( method ) ) { + return MethodType.PRESENCE_CHECKER; + } + else { + return MethodType.OTHER; + } + } + + /** + * Returns {@code true} when the {@link ExecutableElement} is a getter method. A method is a getter when it + * has no parameters, starts + * with 'get' and the return type is any type other than {@code void}, OR the getter starts with 'is' and the type + * returned is a primitive or the wrapper for {@code boolean}. NOTE: the latter does strictly not comply to the bean + * convention. The remainder of the name is supposed to reflect the property name. + *

      + * The calling MapStruct code guarantees that the given method has no arguments. + * + * @param method to be analyzed + * + * @return {@code true} when the method is a getter. + */ + public boolean isGetterMethod(ExecutableElement method) { + if ( !method.getParameters().isEmpty() ) { + // If the method has parameters it can't be a getter + return false; + } + String methodName = method.getSimpleName().toString(); + + boolean isNonBooleanGetterName = methodName.startsWith( "get" ) && methodName.length() > 3 && + method.getReturnType().getKind() != TypeKind.VOID; + + boolean isBooleanGetterName = methodName.startsWith( "is" ) && methodName.length() > 2; + boolean returnTypeIsBoolean = method.getReturnType().getKind() == TypeKind.BOOLEAN || + "java.lang.Boolean".equals( getQualifiedName( method.getReturnType() ) ); + + return isNonBooleanGetterName || ( isBooleanGetterName && returnTypeIsBoolean ); + } + + /** + * Returns {@code true} when the {@link ExecutableElement} is a setter method. A setter starts with 'set'. The + * remainder of the name is supposed to reflect the property name. + *

      + * The calling MapStruct code guarantees that there's only one argument. + * + * @param method to be analyzed + * @return {@code true} when the method is a setter. + */ + public boolean isSetterMethod(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + + return methodName.startsWith( "set" ) && methodName.length() > 3 || isFluentSetter( method ); + } + + protected boolean isFluentSetter(ExecutableElement method) { + if ( method.getParameters().size() != 1 ) { + return false; + } + Element methodOwnerElement = method.getEnclosingElement(); + if ( !canMethodOwnerBeUsedInFluentSetter( methodOwnerElement ) ) { + return false; + } + return !isAdderWithUpperCase4thCharacter( method ) && + typeUtils.isAssignable( method.getReturnType(), methodOwnerElement.asType() ); + } + + private boolean canMethodOwnerBeUsedInFluentSetter(Element methodOwnerElement) { + if ( !( methodOwnerElement instanceof TypeElement ) ) { + return false; + } + + TypeElement typeElement = (TypeElement) methodOwnerElement; + if ( JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName().toString() ).matches() ) { + return false; + } + if ( isKotlinDataClass( typeElement ) ) { + return false; + } + + return true; + } + + private boolean isKotlinDataClass(TypeElement typeElement) { + if ( !KOTLIN_METADATA_JVM_PRESENT ) { + return false; + } + + Metadata kotlinMetadata = typeElement.getAnnotation( Metadata.class ); + if ( kotlinMetadata == null ) { + return false; + } + KotlinClassMetadata classMetadata = KotlinClassMetadata.readLenient( kotlinMetadata ); + if ( classMetadata instanceof KotlinClassMetadata.Class ) { + return Attributes.isData( ( (KotlinClassMetadata.Class) classMetadata ).getKmClass() ); + } + + return false; + } + + /** + * Checks that the method is an adder with an upper case 4th character. The reason for this is that methods such + * as {@code address(String address)} are considered as setter and {@code addName(String name)} too. We need to + * make sure that {@code addName} is considered as an adder and {@code address} is considered as a setter. + * + * @param method the method that needs to be checked + * + * @return {@code true} if the method is an adder with an upper case 4h character, {@code false} otherwise + */ + private boolean isAdderWithUpperCase4thCharacter(ExecutableElement method) { + return isAdderMethod( method ) && Character.isUpperCase( method.getSimpleName().toString().charAt( 3 ) ); + } + + /** + * Returns {@code true} when the {@link ExecutableElement} is an adder method. An adder method starts with 'add'. + * The remainder of the name is supposed to reflect the singular property name (as opposed to plural) of + * its corresponding property. For example: property "children", but "addChild". See also + * {@link #getElementName(ExecutableElement) }. + *

      + * The calling MapStruct code guarantees there's only one argument. + * + * @param method to be analyzed + * + * @return {@code true} when the method is an adder method. + */ + public boolean isAdderMethod(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + + return methodName.startsWith( "add" ) && methodName.length() > 3; + } + + /** + * Returns {@code true} when the {@link ExecutableElement} is a presence check method that checks if the + * corresponding property is present (e.g. not null, not nil, ..). A presence check method method starts with + * 'has'. The remainder of the name is supposed to reflect the property name. + *

      + * The calling MapStruct code guarantees there's no argument and that the return type is boolean or a + * {@link Boolean} + * + * @param method to be analyzed + * @return {@code true} when the method is a presence check method. + */ + public boolean isPresenceCheckMethod(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + return methodName.startsWith( "has" ) && methodName.length() > 3; + } + + /** + * Analyzes the method (getter or setter) and derives the property name. + * See {@link #isGetterMethod(ExecutableElement)} {@link #isSetterMethod(ExecutableElement)}. The first three + * ('get' / 'set' scenario) characters are removed from the simple name, or the first 2 characters ('is' scenario). + * From the remainder the first character is made into small case (to counter camel casing) and the result forms + * the property name. + * + * @param getterOrSetterMethod getter or setter method. + * + * @return the property name. + */ + @Override + public String getPropertyName(ExecutableElement getterOrSetterMethod) { + String methodName = getterOrSetterMethod.getSimpleName().toString(); + if ( isFluentSetter( getterOrSetterMethod ) ) { + // If this is a fluent setter that starts with set and the 4th character is an uppercase one + // then we treat it as a Java Bean style method (we get the property starting from the 4th character). + // Otherwise we treat it as a fluent setter + // For example, for the following methods: + // * public Builder setSettlementDate(String settlementDate) + // * public Builder settlementDate(String settlementDate) + // We are going to extract the same property name settlementDate + if ( methodName.startsWith( "set" ) + && methodName.length() > 3 + && Character.isUpperCase( methodName.charAt( 3 ) ) ) { + return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); + } + else { + return methodName; + } + } + return IntrospectorUtils.decapitalize( methodName.substring( methodName.startsWith( "is" ) ? 2 : 3 ) ); + } + + /** + * Adder methods are used to add elements to collections on a target bean. A typical use case is JPA. The + * convention is that the element name will be equal to the remainder of the add method. Example: 'addElement' + * element name will be 'element'. + * + * @param adderMethod getter or setter method. + * + * @return the property name. + */ + @Override + public String getElementName(ExecutableElement adderMethod) { + String methodName = adderMethod.getSimpleName().toString(); + return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); + } + + /** + * Helper method, to obtain the fully qualified name of a type. + * + * @param type input type + * + * @return fully qualified name of type when the type is a {@link DeclaredType}, null when otherwise. + */ + protected static String getQualifiedName(TypeMirror type) { + DeclaredType declaredType = type.accept( + new SimpleTypeVisitor8() { + @Override + public DeclaredType visitDeclared(DeclaredType t, Void p) { + return t; + } + }, + null + ); + + if ( declaredType == null ) { + return null; + } + + TypeElement typeElement = declaredType.asElement().accept( + new SimpleElementVisitor8() { + @Override + public TypeElement visitType(TypeElement e, Void p) { + return e; + } + }, + null + ); + + return typeElement != null ? typeElement.getQualifiedName().toString() : null; + } + + @Override + public String getCollectionGetterName(String property) { + throw new IllegalStateException( "This method is not intended to be called anymore and will be removed in " + + "future versions." ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java new file mode 100644 index 0000000000..c1126fa9fb --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -0,0 +1,439 @@ +/* + * 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.spi; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.regex.Pattern; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; +import javax.lang.model.util.Types; + +/** + * Default implementation of {@link BuilderProvider}. + * + * The default builder provider considers all public static parameterless methods of a {@link TypeMirror} + * as potential builder creation methods. For each potential builder creation method checks in the return type + * of the method if there exists a method that returns the initial {@link TypeMirror} if such a combination is found + * the {@link BuilderInfo} is created with those 2 methods. + * Example: + *

      
      + * public class Person {
      + *
      + *     private final String firstName;
      + *     private final String lastName;
      + *
      + *     private Person(String firstName, String lastName) {
      + *         this.firstName = firstName;
      + *         this.lastName = lastName;
      + *     }
      + *
      + *     //getters
      + *
      + *     public static Builder builder() {
      + *         return new Builder();
      + *     }
      +
      + *     public static class Builder {
      + *
      + *         private String firstName;
      + *         private String lastName;
      + *
      + *         private Builder() {}
      + *
      + *         //fluent setters
      + *
      + *         public Person create() {
      + *             return new Person( firstName, lastName );
      + *         }
      + *     }
      + * }
      + * 
      + * + * In the example above, when searching for a builder for the {@code Person} type. The {@code Person#builder} method + * would be a builder creation candidate. Then the return type of {@code Person#builder}, {@code Builder}, is + * investigated for a parameterless method that returns {@code Person}. When {@code Builder#create} is found + * the {@link BuilderInfo} is created with the {@code Person#builder} as a builder creation method and + * {@code Builder#create} as a build method. + *

      + * IMPORTANT: Types from the {@code java} and {@code javax} packages are excluded from inspection + * + * @author Filip Hrisafov + */ +public class DefaultBuilderProvider implements BuilderProvider { + + private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); + + protected Elements elementUtils; + protected Types typeUtils; + + @Override + public void init(MapStructProcessingEnvironment processingEnvironment) { + this.elementUtils = processingEnvironment.getElementUtils(); + this.typeUtils = processingEnvironment.getTypeUtils(); + } + + @Override + public BuilderInfo findBuilderInfo(TypeMirror type) { + TypeElement typeElement = getTypeElement( type ); + if ( typeElement == null ) { + return null; + } + + return findBuilderInfo( typeElement ); + } + + /** + * Find the {@link TypeElement} for the given {@link TypeMirror}. + * + * @param type for which the {@link TypeElement} needs to be found/ + * + * @return the type element or {@code null} if the {@link TypeMirror} is not a {@link DeclaredType} + * and the declared type element is not {@link TypeElement} + * + * @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR} + */ + protected TypeElement getTypeElement(TypeMirror type) { + DeclaredType declaredType = getDeclaredType( type ); + return getTypeElement( declaredType ); + } + + /** + * Find the {@link TypeElement} for the given {@link DeclaredType}. + * + * @param declaredType for which the {@link TypeElement} needs to be found. + * @return the type element or {@code null} if the declared type element is not {@link TypeElement} + */ + private TypeElement getTypeElement(DeclaredType declaredType) { + if ( declaredType == null ) { + return null; + } + + return declaredType.asElement().accept( + new SimpleElementVisitor8() { + @Override + public TypeElement visitType(TypeElement e, Void p) { + return e; + } + }, + null + ); + } + + /** + * Find the {@link DeclaredType} for the given {@link TypeMirror}. + * + * @param type for which the {@link DeclaredType} needs to be found. + * @return the declared or {@code null} if the {@link TypeMirror} is not a {@link DeclaredType} + * @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR} + */ + private DeclaredType getDeclaredType(TypeMirror type) { + if ( type.getKind() == TypeKind.ERROR ) { + throw new TypeHierarchyErroneousException( type ); + } + return type.accept( + new SimpleTypeVisitor8() { + @Override + public DeclaredType visitDeclared(DeclaredType t, Void p) { + return t; + } + }, + null + ); + } + + /** + * Find the {@link BuilderInfo} for the given {@code typeElement}. + *

      + * The default implementation iterates over all the methods in {@code typeElement} and uses + * {@link DefaultBuilderProvider#isPossibleBuilderCreationMethod(ExecutableElement, TypeElement)} and + * {@link DefaultBuilderProvider#findBuildMethods(TypeElement, TypeElement)} to create the + * {@link BuilderInfo}. + *

      + * The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the + * {@code typeElement} should be ignored. + *

      + * In case there are multiple {@link BuilderInfo} then a {@link MoreThanOneBuilderCreationMethodException} is + * thrown. + * + * @param typeElement the type element for which a builder searched + * @return the {@link BuilderInfo} or {@code null} if no builder was found for the type + * {@link DefaultBuilderProvider#findBuildMethods(TypeElement, TypeElement)} + * @throws MoreThanOneBuilderCreationMethodException if there are multiple builder creation methods + */ + protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + return findBuilderInfo( typeElement, true ); + } + + protected BuilderInfo findBuilderInfo(TypeElement typeElement, boolean checkParent) { + if ( shouldIgnore( typeElement ) ) { + return null; + } + + // Builder infos which are determined by a static method on the type itself + List methodBuilderInfos = new ArrayList<>(); + // Builder infos which are determined by an inner builder class in the type itself + List innerClassBuilderInfos = new ArrayList<>(); + + for ( Element enclosedElement : typeElement.getEnclosedElements() ) { + if ( ElementKind.METHOD == enclosedElement.getKind() ) { + ExecutableElement method = (ExecutableElement) enclosedElement; + BuilderInfo builderInfo = determineMethodBuilderInfo( method, typeElement ); + if ( builderInfo != null ) { + methodBuilderInfos.add( builderInfo ); + } + } + else if ( ElementKind.CLASS == enclosedElement.getKind() ) { + if ( !methodBuilderInfos.isEmpty() ) { + // Small optimization to not check the inner classes + // if we already have at least one builder through a method + continue; + } + TypeElement classElement = (TypeElement) enclosedElement; + BuilderInfo builderInfo = determineInnerClassBuilderInfo( classElement, typeElement ); + if ( builderInfo != null ) { + innerClassBuilderInfos.add( builderInfo ); + } + } + + } + + if ( methodBuilderInfos.size() == 1 ) { + return methodBuilderInfos.get( 0 ); + } + else if ( methodBuilderInfos.size() > 1 ) { + throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), methodBuilderInfos ); + } + else if ( innerClassBuilderInfos.size() == 1 ) { + return innerClassBuilderInfos.get( 0 ); + } + else if ( innerClassBuilderInfos.size() > 1 ) { + throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), innerClassBuilderInfos ); + } + + if ( checkParent ) { + return findBuilderInfo( typeElement.getSuperclass() ); + } + + return null; + } + + private BuilderInfo determineMethodBuilderInfo(ExecutableElement method, + TypeElement typeElement) { + if ( isPossibleBuilderCreationMethod( method, typeElement ) ) { + TypeElement builderElement = getTypeElement( method.getReturnType() ); + Collection buildMethods = findBuildMethods( builderElement, typeElement ); + if ( !buildMethods.isEmpty() ) { + return new BuilderInfo.Builder() + .builderCreationMethod( method ) + .buildMethod( buildMethods ) + .build(); + } + } + + return null; + } + + private BuilderInfo determineInnerClassBuilderInfo(TypeElement innerClassElement, + TypeElement typeElement) { + if ( innerClassElement.getModifiers().contains( Modifier.PUBLIC ) + && innerClassElement.getModifiers().contains( Modifier.STATIC ) + && innerClassElement.getSimpleName().toString().endsWith( "Builder" ) ) { + for ( Element element : innerClassElement.getEnclosedElements() ) { + if ( ElementKind.CONSTRUCTOR == element.getKind() ) { + ExecutableElement constructor = (ExecutableElement) element; + if ( constructor.getParameters().isEmpty() ) { + // We have a no-arg constructor + // Now check if we have build methods + Collection buildMethods = findBuildMethods( innerClassElement, typeElement ); + if ( !buildMethods.isEmpty() ) { + return new BuilderInfo.Builder() + .builderCreationMethod( constructor ) + .buildMethod( buildMethods ) + .build(); + } + // If we don't have any build methods + // then we can stop since we are only interested in the no-arg constructor + return null; + } + } + } + } + + return null; + } + + /** + * Checks if the {@code method} is a possible builder creation method. + *

      + * The default implementation considers a method as a possible creation method if the following is satisfied: + *

        + *
      • The method has no parameters
      • + *
      • It is a {@code public static} method
      • + *
      • The return type of the {@code method} is not the same as the {@code typeElement}
      • + *
      • + *
      + * + * @param method The method that needs to be checked + * @param typeElement the enclosing element of the method, i.e. the type in which the method is located in + * @return {@code true} if the {@code method} is a possible builder creation method, {@code false} otherwise + */ + protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, TypeElement typeElement) { + return method.getParameters().isEmpty() + && method.getModifiers().contains( Modifier.PUBLIC ) + && method.getModifiers().contains( Modifier.STATIC ) + && method.getReturnType().getKind() != TypeKind.VOID + // Only compare raw elements + // Reason: if the method is a generic method ( Holder build()) and the type element is (Holder) + // then the return type of the method does not match the type of the type element + && !typeUtils.isSameType( + typeUtils.erasure( method.getReturnType() ), + typeUtils.erasure( typeElement.asType() ) + ); + } + + /** + * Searches for a build method for {@code typeElement} within the {@code builderElement}. + *

      + * The default implementation iterates over each method in {@code builderElement} and uses + * {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, DeclaredType, TypeElement)} + * to check if the method is a build method for {@code typeElement}. + *

      + * The default implementation uses {@link DefaultBuilderProvider#shouldIgnore(TypeElement)} to check if the + * {@code builderElement} should be ignored, i.e. not checked for build elements. + * + * @param builderElement the element for the builder + * @param typeElement the element for the type that is being built + * @return the build method for the {@code typeElement} if it exists, or {@code null} if it does not + * {@code build} + */ + protected Collection findBuildMethods(TypeElement builderElement, TypeElement typeElement) { + if ( shouldIgnore( builderElement ) || typeElement == null ) { + return Collections.emptyList(); + } + DeclaredType builderType = getDeclaredType( builderElement.asType() ); + + if ( builderType == null ) { + return Collections.emptyList(); + } + + return findBuildMethods( builderElement, builderType, typeElement ); + } + + private Collection findBuildMethods(TypeElement builderElement, DeclaredType builderType, + TypeElement typeElement) { + if ( shouldIgnore( builderElement ) ) { + return Collections.emptyList(); + } + + List builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() ); + List buildMethods = new ArrayList<>(); + for ( ExecutableElement buildMethod : builderMethods ) { + if ( isBuildMethod( buildMethod, builderType, typeElement ) ) { + buildMethods.add( buildMethod ); + } + } + + if ( !buildMethods.isEmpty() ) { + return buildMethods; + } + + Collection parentClassBuildMethods = findBuildMethods( + getTypeElement( builderElement.getSuperclass() ), + builderType, + typeElement + ); + + if ( !parentClassBuildMethods.isEmpty() ) { + return parentClassBuildMethods; + } + + List interfaces = builderElement.getInterfaces(); + if ( interfaces.isEmpty() ) { + return Collections.emptyList(); + } + + Collection interfaceBuildMethods = new ArrayList<>(); + + for ( TypeMirror builderInterface : interfaces ) { + interfaceBuildMethods.addAll( findBuildMethods( + getTypeElement( builderInterface ), + builderType, + typeElement + ) ); + } + + return interfaceBuildMethods; + } + + /** + * @see #isBuildMethod(ExecutableElement, DeclaredType, TypeElement) + * @deprecated use {@link #isBuildMethod(ExecutableElement, DeclaredType, TypeElement)} instead + */ + @Deprecated + protected boolean isBuildMethod(ExecutableElement buildMethod, TypeElement typeElement) { + return buildMethod.getParameters().isEmpty() && + buildMethod.getModifiers().contains( Modifier.PUBLIC ) + && typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() ); + } + + /** + * Checks if the {@code buildMethod} is a method that creates the {@code typeElement} + * as a member of the {@code builderType}. + *

      + * The default implementation considers a method to be a build method if the following is satisfied: + *

        + *
      • The method has no parameters
      • + *
      • The method is public
      • + *
      • The return type of method is assignable to the {@code typeElement}
      • + *
      + * + * @param buildMethod the method that should be checked + * @param builderType the type of the builder in which the {@code buildMethod} is located in + * @param typeElement the type element that needs to be built + * @return {@code true} if the {@code buildMethod} is a build method for {@code typeElement}, {@code false} + * otherwise + */ + protected boolean isBuildMethod(ExecutableElement buildMethod, DeclaredType builderType, TypeElement typeElement) { + if ( !buildMethod.getParameters().isEmpty() ) { + return false; + } + if ( !buildMethod.getModifiers().contains( Modifier.PUBLIC ) ) { + return false; + } + TypeMirror buildMethodType = typeUtils.asMemberOf( builderType, buildMethod ); + if ( buildMethodType instanceof ExecutableType ) { + return typeUtils.isAssignable( ( (ExecutableType) buildMethodType ).getReturnType(), typeElement.asType() ); + } + return false; + } + + /** + * Whether the {@code typeElement} should be ignored, i.e. not used in inspection. + *

      + * The default implementations ignores {@code null} elements and elements that belong to the {@code java} and + * {@code javax} packages + * @param typeElement that needs to be checked + * @return {@code true} if the element should be ignored, {@code false} otherwise + */ + protected boolean shouldIgnore(TypeElement typeElement) { + return typeElement == null || JAVA_JAVAX_PACKAGE.matcher( typeElement.getQualifiedName() ).matches(); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java new file mode 100644 index 0000000000..11e1257be1 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultEnumMappingStrategy.java @@ -0,0 +1,48 @@ +/* + * 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.spi; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +/** + * The default implementation of the {@link EnumMappingStrategy} service provider interface. + * + * @author Filip Hrisafov + * + * @since 1.4 + */ +public class DefaultEnumMappingStrategy implements EnumMappingStrategy { + + protected Elements elementUtils; + protected Types typeUtils; + + @Override + public void init(MapStructProcessingEnvironment processingEnvironment) { + this.elementUtils = processingEnvironment.getElementUtils(); + this.typeUtils = processingEnvironment.getTypeUtils(); + } + + @Override + public String getDefaultNullEnumConstant(TypeElement enumType) { + return null; + } + + @Override + public String getEnumConstant(TypeElement enumType, String enumConstant) { + return enumConstant; + } + + @Override + public TypeElement getUnexpectedValueMappingExceptionType() { + return elementUtils.getTypeElement( getUnexpectedValueMappingExceptionClass().getCanonicalName() ); + } + + protected Class getUnexpectedValueMappingExceptionClass() { + return IllegalArgumentException.class; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.java new file mode 100644 index 0000000000..8ad6737c63 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/EnumMappingStrategy.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.ap.spi; + +import javax.lang.model.element.TypeElement; + +import org.mapstruct.util.Experimental; + +/** + * A service provider interface for the mapping between different enum constants + * + * @author Arne Seime + * @author Filip Hrisafov + * + * @since 1.4 + */ +@Experimental("This SPI can have its signature changed in subsequent releases") +public interface EnumMappingStrategy { + + /** + * Initializes the enum value mapping strategy + * + * @param processingEnvironment environment for facilities + */ + default void init(MapStructProcessingEnvironment processingEnvironment) { + + } + + /** + * Return the default enum constant to use if the source is null. + * + * @param enumType the enum + * @return enum value or null if there is no designated enum constant + */ + String getDefaultNullEnumConstant(TypeElement enumType); + + /** + * Map the enum constant to the value use for matching. + * In case you want this enum constant to match to null return {@link org.mapstruct.MappingConstants#NULL} + * + * @param enumType the enum this constant belongs to + * @param enumConstant constant to transform + * + * @return the transformed constant - or the original value from the parameter if no transformation is needed. + * never return null + */ + String getEnumConstant(TypeElement enumType, String enumConstant); + + /** + * Return the type element of the exception that should be used in the generated code + * for an unexpected enum constant. + * + * @return the type element of the exception that should be used, never {@code null} + */ + TypeElement getUnexpectedValueMappingExceptionType(); +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.java new file mode 100644 index 0000000000..796bdb4954 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/EnumTransformationStrategy.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.ap.spi; + +import org.mapstruct.util.Experimental; + +/** + * A service provider interface for transforming name based value mappings. + * + * @author Filip Hrisafov + * @since 1.4 + */ +@Experimental("This SPI can have its signature changed in subsequent releases") +public interface EnumTransformationStrategy { + + /** + * Initializes the enum transformation strategy with the MapStruct processing environment. + * + * @param processingEnvironment environment for facilities + */ + default void init(MapStructProcessingEnvironment processingEnvironment) { + + } + + /** + * The name of the strategy. + * + * @return the name of the strategy, never {@code null} + */ + String getStrategyName(); + + /** + * Transform the given value by using the given {@code configuration}. + * + * @param value the value that should be transformed + * @param configuration the configuration that should be used for the transformation + * + * @return the transformed value after applying the configuration + */ + String transform(String value, String configuration); +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java new file mode 100644 index 0000000000..dc14e20de6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.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.ap.spi; + +import javax.lang.model.element.ExecutableElement; + +import org.mapstruct.util.Experimental; + +/** + * Accessor naming strategy for FreeBuilder. + * FreeBuilder adds a lot of other methods that can be considered as fluent setters. Such as: + *

        + *
      • {@code from(Target)}
      • + *
      • {@code mapXXX(UnaryOperator)}
      • + *
      • {@code mutateXXX(Consumer)}
      • + *
      • {@code mergeFrom(Target)}
      • + *
      • {@code mergeFrom(Target.Builder)}
      • + *
      + *

      + * When the JavaBean convention is not used with FreeBuilder then the getters are non-standard and MapStruct + * won't recognize them. Therefore, one needs to use the JavaBean convention in which the fluent setters + * start with {@code set}. + * + * @author Filip Hrisafov + */ +@Experimental("The FreeBuilder accessor naming strategy might change in a subsequent release") +public class FreeBuilderAccessorNamingStrategy extends DefaultAccessorNamingStrategy { + + @Override + protected boolean isFluentSetter(ExecutableElement method) { + // When using FreeBuilder one needs to use the JavaBean convention, which means that all setters will start + // with set + return false; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java new file mode 100644 index 0000000000..1df6ebc1b6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.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.spi; + +import javax.lang.model.element.ExecutableElement; + +import org.mapstruct.util.Experimental; + +/** + * Accessor naming strategy for Immutables. + * The generated Immutables also have a from that works as a copy. Our default strategy considers this method + * as a setter with a name {@code from}. Therefore, we are ignoring it. + * + * @author Filip Hrisafov + */ +@Experimental("The Immutables accessor naming strategy might change in a subsequent release") +public class ImmutablesAccessorNamingStrategy extends DefaultAccessorNamingStrategy { + + @Override + protected boolean isFluentSetter(ExecutableElement method) { + return super.isFluentSetter( method ) && + !method.getSimpleName().toString().equals( "from" ) && + !isPutterWithUpperCase4thCharacter( method ); + } + + private boolean isPutterWithUpperCase4thCharacter(ExecutableElement method) { + return isPutterMethod( method ) && Character.isUpperCase( method.getSimpleName().toString().charAt( 3 ) ); + } + + public boolean isPutterMethod(ExecutableElement method) { + String methodName = method.getSimpleName().toString(); + return methodName.startsWith( "put" ) && methodName.length() > 3; + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java new file mode 100644 index 0000000000..3431808418 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.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.ap.spi; + +import java.util.regex.Pattern; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.util.Experimental; + +/** + * Builder provider for Immutables. A custom provider is needed because Immutables creates an implementation of an + * interface and that implementation has the builder. This implementation would try to find the type created by + * Immutables and would look for the builder in it. Only types annotated with the + * {@code org.immutables.value.Value.Immutable} are considered for this discovery. + * + * @author Filip Hrisafov + */ +@Experimental("The Immutables builder provider might change in a subsequent release") +public class ImmutablesBuilderProvider extends DefaultBuilderProvider { + + private static final Pattern JAVA_JAVAX_PACKAGE = Pattern.compile( "^javax?\\..*" ); + private static final String IMMUTABLE_FQN = "org.immutables.value.Value.Immutable"; + + @Override + protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.length() == 0 || JAVA_JAVAX_PACKAGE.matcher( name ).matches() ) { + return null; + } + + // First look if there is a builder defined in my own type + BuilderInfo info = findBuilderInfo( typeElement, false ); + if ( info != null ) { + return info; + } + + // Check for a builder in the generated immutable type + BuilderInfo immutableInfo = findBuilderInfoForImmutables( typeElement ); + if ( immutableInfo != null ) { + return immutableInfo; + } + + return super.findBuilderInfo( typeElement.getSuperclass() ); + } + + protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement) { + TypeElement immutableAnnotation = elementUtils.getTypeElement( IMMUTABLE_FQN ); + if ( immutableAnnotation != null ) { + return findBuilderInfoForImmutables( + typeElement, + immutableAnnotation + ); + } + return null; + } + + protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement, + TypeElement immutableAnnotation) { + for ( AnnotationMirror annotationMirror : elementUtils.getAllAnnotationMirrors( typeElement ) ) { + if ( typeUtils.isSameType( annotationMirror.getAnnotationType(), immutableAnnotation.asType() ) ) { + TypeElement immutableElement = asImmutableElement( typeElement ); + if ( immutableElement != null ) { + return super.findBuilderInfo( immutableElement, false ); + } + else { + // Immutables processor has not run yet. Trigger a postpone to the next round for MapStruct + throw new TypeHierarchyErroneousException( typeElement ); + } + } + } + return null; + } + + protected TypeElement asImmutableElement(TypeElement typeElement) { + Element enclosingElement = typeElement.getEnclosingElement(); + StringBuilder builderQualifiedName = new StringBuilder( typeElement.getQualifiedName().length() + 17 ); + if ( enclosingElement.getKind() == ElementKind.PACKAGE ) { + builderQualifiedName.append( ( (PackageElement) enclosingElement ).getQualifiedName().toString() ); + } + else { + builderQualifiedName.append( ( (TypeElement) enclosingElement ).getQualifiedName().toString() ); + } + + if ( builderQualifiedName.length() > 0 ) { + builderQualifiedName.append( "." ); + } + + builderQualifiedName.append( "Immutable" ).append( typeElement.getSimpleName() ); + return elementUtils.getTypeElement( builderQualifiedName ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java new file mode 100644 index 0000000000..18086e8591 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java @@ -0,0 +1,48 @@ +/* + * 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.spi; + +import java.util.Map; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +/** + * MapStruct will provide the implementations of its SPIs with on object implementing this interface so they can use + * facilities provided by it. It is a subset of {@link javax.annotation.processing.ProcessingEnvironment + * ProcessingEnvironment}. + * + * @author Filip Hrisafov + * @see javax.annotation.processing.ProcessingEnvironment + * + * @since 1.3 + */ +public interface MapStructProcessingEnvironment { + + /** + * Returns an implementation of some utility methods for + * operating on elements + * + * @return element utilities + */ + Elements getElementUtils(); + + /** + * Returns an implementation of some utility methods for + * operating on types. + * + * @return type utilities + */ + Types getTypeUtils(); + + /** + * Returns the resolved options specified by the impl of + * {@link AdditionalSupportedOptionsProvider}. + * + * @return resolved options + */ + Map getOptions(); + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java new file mode 100644 index 0000000000..2a1cacfb1f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java @@ -0,0 +1,51 @@ +/* + * 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.spi; + +import javax.lang.model.element.TypeElement; + +import org.mapstruct.util.Experimental; + +/** + * A service provider interface that is used to control if MapStruct is allowed to generate automatic sub-mapping for + * a given {@link TypeElement}. + *

      + * 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.
      • + *
      • 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.
      • + *
      + *

      + * With this SPI the last step before raising an error can be controlled. i.e. A user can control whether MapStruct + * is allowed to generate such automatic sub-mapping method (for the source or target type) or not. + * + * @author Filip Hrisafov + * @since 1.2 + */ +@Experimental("This SPI can have its signature changed in subsequent releases") +public interface MappingExclusionProvider { + + /** + * Checks if MapStruct should not generate an automatic sub-mapping for the provided {@link TypeElement}, i.e. + * MapStruct will not try to descent into this class and won't try to automatically map it with some other type. + * The given {@code typeElement} will be excluded from the automatic sub-mapping generation + * + * @param typeElement that needs to be checked + * @return {@code true} if MapStruct should exclude the provided {@link TypeElement} from an automatic sub-mapping + */ + boolean isExcluded(TypeElement typeElement); +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MethodType.java b/processor/src/main/java/org/mapstruct/ap/spi/MethodType.java index c53d330cea..d2b93e615e 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MethodType.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MethodType.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.spi; @@ -43,5 +30,10 @@ public enum MethodType { /** * Any method which is neither a JavaBeans getter, setter nor an adder method. */ - OTHER; + OTHER, + + /** + * A method to check whether a property is present, e.g. {@code public String hasName()}. + */ + PRESENCE_CHECKER; } diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MoreThanOneBuilderCreationMethodException.java b/processor/src/main/java/org/mapstruct/ap/spi/MoreThanOneBuilderCreationMethodException.java new file mode 100644 index 0000000000..a869dd9fd6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/MoreThanOneBuilderCreationMethodException.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.spi; + +import java.util.List; +import javax.lang.model.type.TypeMirror; + +/** + * Indicates that a type has too many builder creation methods. + * This exception can be used to signal the MapStruct processor that more than one builder creation method was found. + * + * @author Filip Hrisafov + */ +public class MoreThanOneBuilderCreationMethodException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final TypeMirror type; + private final List builderCreationMethods; + + public MoreThanOneBuilderCreationMethodException(TypeMirror type, List builderCreationMethods) { + this.type = type; + this.builderCreationMethods = builderCreationMethods; + } + + public TypeMirror getType() { + return type; + } + + public List getBuilderInfo() { + return builderCreationMethods; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/NoOpBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/NoOpBuilderProvider.java new file mode 100644 index 0000000000..a30346c4b0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/NoOpBuilderProvider.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.ap.spi; + +// tag::documentation[] + +import javax.lang.model.type.TypeMirror; + +// end::documentation[] + +/** + * A NoOp {@link BuilderProvider} which returns {@code null} when searching for a builder. + * + * @author Filip Hrisafov + */ +// tag::documentation[] +public class NoOpBuilderProvider implements BuilderProvider { + + @Override + public BuilderInfo findBuilderInfo(TypeMirror type) { + return null; + } + +} +// end::documentation[] diff --git a/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.java new file mode 100644 index 0000000000..226251dfab --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/PrefixEnumTransformationStrategy.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.ap.spi; + +/** + * An {@link EnumTransformationStrategy} that prepends a prefix to the enum value. + * + * @author Filip Hrisafov + * + * @since 1.4 + */ +public class PrefixEnumTransformationStrategy implements EnumTransformationStrategy { + + @Override + public String getStrategyName() { + return "prefix"; + } + + @Override + public String transform(String value, String configuration) { + return configuration + value; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.java new file mode 100644 index 0000000000..dec7af6de4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/StripPrefixEnumTransformationStrategy.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.ap.spi; + +/** + * An {@link EnumTransformationStrategy} that strips a prefix from the enum value. + * + * @author Filip Hrisafov + * + * @since 1.4 + */ +public class StripPrefixEnumTransformationStrategy implements EnumTransformationStrategy { + + @Override + public String getStrategyName() { + return "stripPrefix"; + } + + @Override + public String transform(String value, String configuration) { + if ( value.startsWith( configuration ) ) { + return value.substring( configuration.length() ); + } + return value; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.java new file mode 100644 index 0000000000..3b76354fa7 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/StripSuffixEnumTransformationStrategy.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.ap.spi; + +/** + * An {@link EnumTransformationStrategy} that strips a suffix from the enum value. + * + * @author Filip Hrisafov + * + * @since 1.4 + */ +public class StripSuffixEnumTransformationStrategy implements EnumTransformationStrategy { + + @Override + public String getStrategyName() { + return "stripSuffix"; + } + + @Override + public String transform(String value, String configuration) { + if ( value.endsWith( configuration ) ) { + return value.substring( 0, value.length() - configuration.length() ); + } + return value; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.java new file mode 100644 index 0000000000..1f4eb89217 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/SuffixEnumTransformationStrategy.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.ap.spi; + +/** + * An {@link EnumTransformationStrategy} that appends a suffix to the enum value. + * + * @author Filip Hrisafov + * + * @since 1.4 + */ +public class SuffixEnumTransformationStrategy implements EnumTransformationStrategy { + + @Override + public String getStrategyName() { + return "suffix"; + } + + @Override + public String transform(String value, String configuration) { + return value + configuration; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/TypeHierarchyErroneousException.java b/processor/src/main/java/org/mapstruct/ap/spi/TypeHierarchyErroneousException.java new file mode 100644 index 0000000000..aa9abc46a2 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/TypeHierarchyErroneousException.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.spi; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +/** + * Indicates a type was visited whose hierarchy was erroneous, because it has a non-existing super-type. + *

      + * This exception can be used to signal the MapStruct processor to postpone the generation of the mappers to the next + * round + * + * @author Gunnar Morling + */ +public class TypeHierarchyErroneousException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + private final TypeMirror type; + + public TypeHierarchyErroneousException(TypeElement element) { + this( element.asType() ); + } + + public TypeHierarchyErroneousException(TypeMirror type) { + this.type = type; + } + + public TypeMirror getType() { + return type; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/package-info.java b/processor/src/main/java/org/mapstruct/ap/spi/package-info.java index 8fe1116768..ca4ed5db3b 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/package-info.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/package-info.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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 */ /** *

      diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java new file mode 100644 index 0000000000..95dcd7a92d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java @@ -0,0 +1,47 @@ +/* + * 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.spi.util; + +/** + * Utilities for tools to learn about the properties, events, and methods supported by a target Java Bean. + * It is mainly needed to avoid using {@link java.beans.Introspector} class which is part of java.desktop module + * from java 9, thus avoiding pulling a module ( of around 10 MB ) for using a single class. + * + * @author Saheb Preet Singh + */ +public class IntrospectorUtils { + + private IntrospectorUtils() { + } + + /** + * Utility method to take a string and convert it to normal Java variable + * name capitalization. This normally means converting the first + * character from upper case to lower case, but in the (unusual) special + * case when there is more than one character and both the first and + * second characters are upper case, we leave it alone. + *

      + * Thus "FooBah" becomes "fooBah" and "X" becomes "x", but "URL" stays + * as "URL". + * + * @param name The string to be decapitalized. + * + * @return The decapitalized version of the string. + */ + public static String decapitalize(String name) { + if ( name == null || name.isEmpty() ) { + return name; + } + if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) && + Character.isUpperCase( name.charAt( 0 ) ) ) { + return name; + } + char[] chars = name.toCharArray(); + chars[0] = Character.toLowerCase( chars[0] ); + return new String( chars ); + } + +} diff --git a/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.java new file mode 100644 index 0000000000..93bccf51d0 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/package-info.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 + */ + +/** + * Utility classes for the SPI package. + */ +package org.mapstruct.ap.spi.util; diff --git a/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors b/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors new file mode 100644 index 0000000000..172d1bb777 --- /dev/null +++ b/processor/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -0,0 +1 @@ +org.mapstruct.ap.MappingProcessor,isolating diff --git a/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor index b8c04ecdc9..c472c18f91 100644 --- a/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1,18 +1,5 @@ -# Copyright 2012-2015 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.ap.MappingProcessor diff --git a/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor index faccd7a3e3..d5234fca5b 100644 --- a/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor +++ b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor @@ -1,22 +1,11 @@ -# Copyright 2012-2015 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.ap.internal.processor.CdiComponentProcessor +org.mapstruct.ap.internal.processor.JakartaCdiComponentProcessor org.mapstruct.ap.internal.processor.Jsr330ComponentProcessor +org.mapstruct.ap.internal.processor.JakartaComponentProcessor org.mapstruct.ap.internal.processor.MapperCreationProcessor org.mapstruct.ap.internal.processor.MapperRenderingProcessor org.mapstruct.ap.internal.processor.MethodRetrievalProcessor diff --git a/processor/src/main/resources/META-INF/services/org.mapstruct.ap.spi.EnumTransformationStrategy b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.spi.EnumTransformationStrategy new file mode 100644 index 0000000000..264b82d744 --- /dev/null +++ b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.spi.EnumTransformationStrategy @@ -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 + +org.mapstruct.ap.spi.PrefixEnumTransformationStrategy +org.mapstruct.ap.spi.StripPrefixEnumTransformationStrategy +org.mapstruct.ap.spi.StripSuffixEnumTransformationStrategy +org.mapstruct.ap.spi.SuffixEnumTransformationStrategy +org.mapstruct.ap.spi.CaseEnumTransformationStrategy diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl new file mode 100644 index 0000000000..6753a73d6f --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl @@ -0,0 +1,14 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private DecimalFormat ${name}( <#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { + + DecimalFormat df = new DecimalFormat( numberFormat<#if parameters.size() > 1>, DecimalFormatSymbols.getInstance( locale ) ); + df.setParseBigDecimal( true ); + return df; +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/conversion/GetDateTimeFormatterField.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/GetDateTimeFormatterField.ftl new file mode 100644 index 0000000000..6efbe7f0ff --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/GetDateTimeFormatterField.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingField" --> +private final <@includeModel object=type/> ${variableName} = <@includeModel object=type/>.ofPattern( "${templateParameter['dateFormat']}" ); \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedConstructor.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedConstructor.ftl new file mode 100644 index 0000000000..e3ddd51bd7 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedConstructor.ftl @@ -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 + +--> + <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.AnnotatedConstructor" --> + <#if noArgumentConstructor??> + <@includeModel object=noArgumentConstructor/> + + +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + +public ${name}(<#list mapperReferences as mapperReference><#list mapperReference.annotations as annotation><@includeModel object=annotation/> <@includeModel object=mapperReference.type/> ${mapperReference.variableName}<#if mapperReference_has_next>, ) { + <#if noArgumentConstructor?? && !noArgumentConstructor.fragments.empty>this(); + <#list mapperReferences as mapperReference> + this.${mapperReference.variableName} = ${mapperReference.variableName}; + + <#list fragments as fragment> + <#nt><@includeModel object=fragment/> + +} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedSetter.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedSetter.ftl new file mode 100644 index 0000000000..74d4241fa5 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotatedSetter.ftl @@ -0,0 +1,14 @@ + <#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> + <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.AnnotatedSetter" --> + <#list methodAnnotations as annotation> + <#nt><@includeModel object=annotation/> + +public void set${fieldName?cap_first}(<#list parameterAnnotations as annotation><#nt><@includeModel object=annotation/> <@includeModel object=type/> ${fieldName}) { + this.${fieldName} = ${fieldName}; +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl index 8b9283401f..0b6737fbe4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Annotation.ftl @@ -1,21 +1,20 @@ <#-- - Copyright 2012-2015 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 --> -@<@includeModel object=type/><#if (properties?size > 0) >(<#list properties as property>${property}<#if property_has_next>, ) \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" --> +<#switch properties?size> + <#on 0> + @<@includeModel object=type/><#rt> + <#on 1> + @<@includeModel object=type/>(<@includeModel object=properties[0]/>)<#rt> + <#default> + @<@includeModel object=type/>( + <#list properties as property> + <#nt><@includeModel object=property/><#if property_has_next>, + + )<#rt> + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl index 8a1de2400b..616c4900e6 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/AnnotationMapperReference.ftl @@ -1,24 +1,14 @@ <#-- - Copyright 2012-2015 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 --> -<#list annotations as annotation> -<#nt><@includeModel object=annotation/> - -private <@includeModel object=type/> ${variableName}; \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.AnnotationMapperReference" --> +<#if includeAnnotationsOnField> + <#list annotations as annotation> + <#nt><@includeModel object=annotation/> + + +private <#if fieldFinal>final <@includeModel object=type/> ${variableName}; \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index bc56e2e565..fd101f0d95 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl @@ -1,67 +1,151 @@ <#-- - Copyright 2012-2015 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 --> -@Override +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.BeanMappingMethod" --> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { + <#assign targetType = resultType /> + <#if !existingInstanceMapping> + <#assign targetType = returnTypeToConstruct /> + <#list beforeMappingReferencesWithoutMappingTarget as callback> - <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + <@includeModel object=callback targetBeanName=resultName targetType=targetType/> + <#if !callback_has_next> + + + + <#list beforeMappingReferencesWithFinalizedReturnType as callback> + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> <#if !callback_has_next> - <#if !mapNullToDefault> - if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { - return<#if returnType.name != "void"> null; + <#if !mapNullToDefault && !sourcePresenceChecks.empty> + if ( <#list sourcePresenceChecks as sourcePresenceCheck><@includeModel object=sourcePresenceCheck.negate() /><#if sourcePresenceCheck_has_next> && ) { + <#if returnType.name == "void"> + return; + <#else> + <#if existingInstanceMapping> + <@createReturn applyOptionalAfterMapping=false>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /> + <#else> + return ${returnType.null}; + + } + <#if hasSubclassMappings()> + <#list subclassMappings as subclass> + <#if subclass_index > 0>else if (${subclass.sourceArgument} instanceof <@includeModel object=subclass.sourceType/>) { + <@includeModel object=subclass.assignment existingInstanceMapping=existingInstanceMapping/> + } + + else { + + <#if isAbstractReturnType()> + throw new <@includeModel object=subclassExhaustiveException />("Not all subclasses are supported for this mapping. Missing for " + ${subclassMappings[0].sourceArgument}.getClass()); + <#else> <#if !existingInstanceMapping> - <@includeModel object=resultType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType/><#else>new <@includeModel object=resultType/>(); + <#if hasConstructorMappings()> + <#if (sourceParameters?size > 1)> + <#list sourceParametersNeedingPresenceCheck as sourceParam> + <#if (constructorPropertyMappingsByParameter(sourceParam)?size > 0)> + <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> + <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; + + if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParam)!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParam.name}.get(); + + + <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> + <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> + + } + + + <#list sourceParametersNotNeedingPresenceCheck as sourceParam> + <#if (constructorPropertyMappingsByParameter(sourceParam)?size > 0)> + <#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping> + <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; + <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> + + + + <#else> + <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping> + <@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null}; + + <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParameters[0])!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParameters[0].name}.get(); + + + <#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping> + <@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> + + <#if mapNullToDefault> + } + + + <#list constructorConstantMappings as constantMapping> + + <@compress single_line=true> + <@includeModel object=constantMapping.targetType /> <@includeModel object=constantMapping existingInstanceMapping=existingInstanceMapping/> + + + + + <@includeModel object=returnTypeToConstruct/> ${resultName} = <@includeModel object=factoryMethod targetType=returnTypeToConstruct/>; + <#else > + <@includeModel object=returnTypeToConstruct/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=returnTypeToConstruct/><#else><@includeModel object=newInstance/>(); + <#list beforeMappingReferencesWithMappingTarget as callback> - <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + <@includeModel object=callback targetBeanName=resultName targetType=targetType/> <#if !callback_has_next> <#if (sourceParameters?size > 1)> - <#list sourceParametersExcludingPrimitives as sourceParam> - <#if (propertyMappingsByParameter[sourceParam.name]?size > 0)> - if ( ${sourceParam.name} != null ) { - <#list propertyMappingsByParameter[sourceParam.name] as propertyMapping> + <#list sourceParametersNeedingPresenceCheck as sourceParam> + <#if (propertyMappingsByParameter(sourceParam)?size > 0)> + if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParam)!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParam.name}.get(); + + + <#list propertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> } - <#list sourcePrimitiveParameters as sourceParam> - <#if (propertyMappingsByParameter[sourceParam.name]?size > 0)> - <#list propertyMappingsByParameter[sourceParam.name] as propertyMapping> + <#list sourceParametersNotNeedingPresenceCheck as sourceParam> + <#if (propertyMappingsByParameter(sourceParam)?size > 0)> + <#list propertyMappingsByParameter(sourceParam) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> - <#else> - <#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) { - <#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping> + <#elseif !propertyMappingsByParameter(sourceParameters[0]).empty> + <#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) { + <#assign sourceParamReassignment = getSourceParameterReassignment(sourceParameters[0])!'' /> + <#if sourceParamReassignment?has_content> + <@includeModel object=sourceParamReassignment.type /> ${sourceParamReassignment.name} = ${sourceParameters[0].name}.get(); + + + <#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping> <@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/> <#if mapNullToDefault>} @@ -73,18 +157,62 @@ <#if callback_index = 0> - <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + <@includeModel object=callback targetBeanName=resultName targetType=targetType/> <#if returnType.name != "void"> - return ${resultName}; + <#if finalizerMethod??> + <#if (afterMappingReferencesWithFinalizedReturnType?size > 0)> + <@includeModel object=finalizedReturnType /> ${finalizedResultName} = ${resultName}.<@includeModel object=finalizerMethod />; + + <#list afterMappingReferencesWithFinalizedReturnType as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> + + + <@createReturn>${finalizedResultName} + <#else> + <@createReturn>${resultName}.<@includeModel object=finalizerMethod /> + + <#else> + <@createReturn>${resultName} + + + + <#if hasSubclassMappings()> + } } <#macro throws> <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> <#list thrownTypes as exceptionType> <@includeModel object=exceptionType/> - <#if exceptionType_has_next>, + <#if exceptionType_has_next>, <#t> + +<#macro createReturn applyOptionalAfterMapping=true> +<#-- <@compress single_line=true>--> + <#if returnType.optionalType> + <#if (afterMappingReferencesWithOptionalReturnType?size > 0)> + <@includeModel object=returnType /> ${optionalResultName} = <@includeModel object=returnType.asRawType()/>.of( <#nested/> ); + + <#list afterMappingReferencesWithOptionalReturnType as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=optionalResultName targetType=returnType/> + + + return ${optionalResultName}; + <#else> + return <@includeModel object=returnType.asRawType()/>.of( <#nested/> ); + + <#else> + return <#nested/>; + +<#-- --> + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ConversionMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ConversionMethod.ftl index 1d9fc45105..14d0e4883d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ConversionMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ConversionMethod.ftl @@ -1,21 +1,8 @@ <#-- - Copyright 2012-2015 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 --> ${name}<#if ext.input??>( ${ext.input} )<#else>() \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/DecoratorConstructor.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/DecoratorConstructor.ftl index 7cec67191a..9c9f397c29 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/DecoratorConstructor.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/DecoratorConstructor.ftl @@ -1,23 +1,11 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.DecoratorConstructor" --> public ${name}() { this( new ${delegateName}() ); } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/DefaultMapperReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/DefaultMapperReference.ftl index 176916cec2..b2ab4cb6f0 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/DefaultMapperReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/DefaultMapperReference.ftl @@ -1,21 +1,9 @@ <#-- - Copyright 2012-2015 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 --> -private final <@includeModel object=type/> ${variableName} = <#if annotatedMapper>Mappers.getMapper( <@includeModel object=type/>.class );<#else>new <@includeModel object=type/>(); \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.DefaultMapperReference" --> +private final <@includeModel object=type/> ${variableName} = <#if singleton><@includeModel object=type/>.INSTANCE;<#else><#if annotatedMapper>Mappers.getMapper( <@includeModel object=type/>.class );<#else>new <@includeModel object=type/>(); \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/DelegatingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/DelegatingMethod.ftl index bfa04fab7c..f27c242de1 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/DelegatingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/DelegatingMethod.ftl @@ -1,33 +1,21 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.DelegatingMethod" --> @Override public <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) <@throws/> { - return delegate.${name}( <#list parameters as param>${param.name}<#if param_has_next>, ); + <#if returnType.name != "void">return delegate.${name}( <#list parameters as param>${param.name}<#if param_has_next>, ); } <#macro throws> <@compress single_line=true> <#if (thrownTypes?size > 0)>throws <#list thrownTypes as exceptionType> <@includeModel object=exceptionType/> - <#if exceptionType_has_next>, + <#if exceptionType_has_next>, <#t> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Direct.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Direct.ftl deleted file mode 100644 index 3a307bbb50..0000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Direct.ftl +++ /dev/null @@ -1,21 +0,0 @@ -<#-- - - Copyright 2012-2015 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. - ---> -${sourceReference} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/EnumMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/EnumMappingMethod.ftl deleted file mode 100644 index b3ca32173f..0000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/EnumMappingMethod.ftl +++ /dev/null @@ -1,56 +0,0 @@ -<#-- - - Copyright 2012-2015 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. - ---> -@Override -public <@includeModel object=returnType/> ${name}(<@includeModel object=sourceParameter/>) { - <#list beforeMappingReferencesWithoutMappingTarget as callback> - <@includeModel object=callback targetBeanName=resultName targetType=resultType/> - <#if !callback_has_next> - - - - if ( ${sourceParameter.name} == null ) { - return null; - } - - <@includeModel object=resultType/> ${resultName}; - - switch ( ${sourceParameter.name} ) { - <#list enumMappings as enumMapping> - case ${enumMapping.source}: ${resultName} = <@includeModel object=returnType/>.${enumMapping.target}; - break; - - default: throw new IllegalArgumentException( "Unexpected enum constant: " + ${sourceParameter.name} ); - } - <#list beforeMappingReferencesWithMappingTarget as callback> - <#if callback_index = 0> - - - <@includeModel object=callback targetBeanName=resultName targetType=resultType/> - - <#list afterMappingReferences as callback> - <#if callback_index = 0> - - - <@includeModel object=callback targetBeanName=resultName targetType=resultType/> - - - return ${resultName}; -} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Field.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Field.ftl index aa81400e1e..af4cb01f63 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/Field.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Field.ftl @@ -1,21 +1,9 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Field" --> private final <@includeModel object=type/> ${variableName}; \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl new file mode 100644 index 0000000000..50ab97e5b4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/FromOptionalTypeConversion.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.FromOptionalTypeConversion" --> +<@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl index b6af86c226..c65b3e5f85 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/GeneratedType.ftl @@ -1,38 +1,31 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.GeneratedType" --> +<#if hasPackageName()> package ${packageName}; + -<#list importTypes as importedType> -import ${importedType.importName}; +<#list importTypeNames as importedType> +import ${importedType}; +<#if javadoc??><#nt><@includeModel object=javadoc/> +<#if !generatedTypeAvailable>/* @Generated( value = "org.mapstruct.ap.MappingProcessor"<#if suppressGeneratorTimestamp == false>, date = "${.now?string("yyyy-MM-dd'T'HH:mm:ssZ")}"<#if suppressGeneratorVersionComment == false>, comments = "version: ${versionInformation.mapStructVersion}, compiler: ${versionInformation.compiler}, environment: Java ${versionInformation.runtimeVersion} (${versionInformation.runtimeVendor})" -) +)<#if !generatedTypeAvailable> +*/ <#list annotations as annotation> <#nt><@includeModel object=annotation/> -<#lt>${accessibility.keyword} class ${name}<#if superClassName??> extends ${superClassName}<#if interfaceName??> implements ${interfaceName} { +<#lt>${accessibility.keyword} class ${name} <#if mapperDefinitionType.interface>implements<#else>extends <@includeModel object=mapperDefinitionType/> { <#list fields as field><#if field.used><#nt> <@includeModel object=field/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl new file mode 100644 index 0000000000..aff9641d60 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.IterableCreation" --> +<@compress single_line=true> + <#if factoryMethod??> + <@includeModel object=factoryMethod targetType=resultType/> + <#elseif enumSet> + EnumSet.noneOf( <@includeModel object=enumSetElementType raw=true/>.class ) + <#elseif resultType.implementation??> + <#if resultType.implementation.factoryMethodName?? && ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize> + <@includeModel object=resultType.implementationType raw=true />.${resultType.implementation.factoryMethodName}( <@sizeForCreation /> ) + <#else> + <@includeModel object=newInstance/><#if ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize>( <@sizeForCreation /> )<#else>() + + <#else> + <@includeModel object=newInstance/>() + + +<#macro sizeForCreation> + <@compress single_line=true> + <#if loadFactorAdjustment> + Math.max( (int) ( <@iterableSize/> / .75f ) + 1, 16 ) + <#else> + <@iterableSize/> + + + +<#macro iterableSize> + <@compress single_line=true> + <#if sourceParameter.type.arrayType> + ${sourceParameter.name}.length + <#else> + ${sourceParameter.name}.size() + + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl index 99dc3e0ef4..6268366f7b 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableMappingMethod.ftl @@ -1,24 +1,15 @@ <#-- - Copyright 2012-2015 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 --> -<#if overridden>@Override +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.IterableMappingMethod" --> +<#import "macro/CommonMacros.ftl" as lib> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + <#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> @@ -26,42 +17,43 @@ - if ( ${sourceParameter.name} == null ) { + <#if sourceParameterPresenceCheck??> + if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) { <#if !mapNullToDefault> - <#-- returned target type starts to miss-align here with target handed via param, TODO is this right? --> - return<#if returnType.name != "void"> null; + return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; <#else> <#if resultType.arrayType> <#if existingInstanceMapping> <#-- we can't clear an existing array, so we've got to clear by setting values to default --> for (int ${index2Name} = 0; ${index2Name} < ${resultName}.length; ${index2Name}++ ) { - ${resultName}[${index2Name}] = ${defaultValue}; + ${resultName}[${index2Name}] = ${resultElementType.null}; } return<#if returnType.name != "void"> ${resultName}; <#else> - return new <@includeModel object=resultElementType/>[0]; + return <@lib.constructArrayType targetType=resultType targetSize=0/>; <#else> <#if existingInstanceMapping> ${resultName}.clear(); return<#if returnType.name != "void"> ${resultName}; <#else> - return <@iterableCreation/>; + return <@includeModel object=iterableCreation useSizeIfPossible=false/>; } + <#if resultType.arrayType> <#if !existingInstanceMapping> - <@includeModel object=resultElementType/>[] ${resultName} = new <@includeModel object=resultElementType/>[<@iterableSize/>]; + <@includeModel object=resultElementType/>[] ${resultName} = <@lib.constructArrayType targetType=resultType targetSize=iterableSize()/>; <#else> <#if existingInstanceMapping> ${resultName}.clear(); <#else> <#-- Use the interface type on the left side, except it is java.lang.Iterable; use the implementation type - if present - on the right side --> - <@iterableLocalVarDef/> ${resultName} = <@iterableCreation/>; + <@iterableLocalVarDef/> ${resultName} = <@includeModel object=iterableCreation useSizeIfPossible=true/>; <#list beforeMappingReferencesWithMappingTarget as callback> @@ -74,7 +66,7 @@ int ${index1Name} = 0; for ( <@includeModel object=sourceElementType/> ${loopVariableName} : ${sourceParameter.name} ) { <#if existingInstanceMapping> - if ( ( ${index1Name} >= ${resultName}.length ) || ( ${index1Name} >= <@iterableSize/> ) ) { + if ( ( ${index1Name} >= ${resultName}.length ) || ( ${index1Name} >= ${iterableSize()} ) ) { break; } @@ -101,19 +93,17 @@ <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> <#list thrownTypes as exceptionType> <@includeModel object=exceptionType/> - <#if exceptionType_has_next>, + <#if exceptionType_has_next>, <#t> -<#macro iterableSize> - <@compress single_line=true> - <#if sourceParameter.type.arrayType> - ${sourceParameter.name}.length - <#else> - ${sourceParameter.name}.size() - - - +<#function iterableSize> + <#if sourceParameter.type.arrayType> + <#return sourceParameter.name + ".length"> + <#else> + <#return sourceParameter.name + ".size()"> + + <#macro iterableLocalVarDef> <@compress single_line=true> <#if resultType.fullyQualifiedName == "java.lang.Iterable"> @@ -122,18 +112,4 @@ <@includeModel object=resultType/> - -<#macro iterableCreation> - <@compress single_line=true> - <#if factoryMethod??> - <@includeModel object=factoryMethod targetType=resultType/> - <#else> - new - <#if resultType.implementationType??> - <@includeModel object=resultType.implementationType/> - <#else> - <@includeModel object=resultType/> - () - - - + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl new file mode 100644 index 0000000000..89ac57b9ef --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/Javadoc.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Javadoc" --> +/** +<#list value?split("\n") as line><#nt>*<#if line?has_content> ${line?trim} + +<#if !authors.isEmpty()> +* +<#list authors as author> <#nt>* @author ${author?trim} + + +<#if deprecated?has_content> +* +<#nt>* @deprecated ${deprecated?trim} + +<#if since?has_content> +* +<#nt>* @since ${since?trim} + +<#nt> */ \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.ftl index 9e2937cc49..310eda210d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.ftl @@ -1,21 +1,18 @@ <#-- - Copyright 2012-2015 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 --> -<#if declaringType??>${instanceVariableName}.${name}(<#list parameterAssignments as param> <#if param.targetType><@includeModel object=ext.targetType raw=true/>.class<#elseif param.mappingTarget>${ext.targetBeanName}<#else>${param.name}<#if param_has_next>,<#else> ); +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.LifecycleCallbackMethodReference" --> +<@compress single_line=true> + <#if hasReturnType()> + <@includeModel object=methodResultType /> ${targetVariableName} = + + <#include 'MethodReference.ftl'>; + +<#if hasReturnType()><#nt> +if ( ${targetVariableName} != null ) { + return<#if methodReturnType.name != "void"> ${targetVariableName}; +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index 11edf9831c..e04d1b9475 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl @@ -1,24 +1,14 @@ <#-- - Copyright 2012-2015 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 --> -<#if overridden>@Override +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MapMappingMethod" --> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + <#lt>${accessibility.keyword} <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=resultType/> @@ -26,23 +16,25 @@ - if ( ${sourceParameter.name} == null ) { + <#if sourceParameterPresenceCheck??> + if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) { <#if !mapNullToDefault> - return<#if returnType.name != "void"> null; + return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; <#else> <#if existingInstanceMapping> ${resultName}.clear(); return<#if returnType.name != "void"> ${resultName}; <#else> - return <@returnObjectCreation/>; + return <@includeModel object=iterableCreation useSizeIfPossible=false/>; } + <#if existingInstanceMapping> ${resultName}.clear(); <#else> - <@includeModel object=resultType /> ${resultName} = <@returnObjectCreation/>; + <@includeModel object=resultType /> ${resultName} = <@includeModel object=iterableCreation useSizeIfPossible=true/>; <#list beforeMappingReferencesWithMappingTarget as callback> @@ -52,15 +44,15 @@ <#-- Once #148 has been addressed, the simple name of Map.Entry can be used --> - for ( java.util.Map.Entry<<#list sourceParameter.type.typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > ${entryVariableName} : ${sourceParameter.name}.entrySet() ) { + for ( java.util.Map.Entry<<#list sourceElementTypes as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > ${entryVariableName} : ${sourceParameter.name}.entrySet() ) { <#-- key --> <@includeModel object=keyAssignment targetWriteAccessorName=keyVariableName - targetType=resultType.typeParameters[0].typeBound/> + targetType=resultElementTypes[0].typeBound/> <#-- value --> <@includeModel object=valueAssignment targetWriteAccessorName=valueVariableName - targetType=resultType.typeParameters[1].typeBound/> + targetType=resultElementTypes[1].typeBound/> ${resultName}.put( ${keyVariableName}, ${valueVariableName} ); } <#list afterMappingReferences as callback> @@ -78,21 +70,7 @@ <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> <#list thrownTypes as exceptionType> <@includeModel object=exceptionType/> - <#if exceptionType_has_next>, + <#if exceptionType_has_next>, <#t> - -<#macro returnObjectCreation> - <@compress single_line=true> - <#if factoryMethod??> - <@includeModel object=factoryMethod targetType=resultType/> - <#else> - new - <#if resultType.implementationType??> - <@includeModel object=resultType.implementationType /> - <#else> - <@includeModel object=resultType /> - () - - - + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl index 701268cea2..63f983df46 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl @@ -1,57 +1,77 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReference" --> <@compress single_line=true> <#-- method is either internal to the mapper class, or external (via uses) declaringMapper!=null --> - <#if declaringMapper??><#if static><@includeModel object=declaringMapper.type/><#else>${mapperVariableName}.<@params/> + <#if declaringMapper??> + <#if static><@includeModel object=declaringMapper.type/><#else>${mapperVariableName}.<@methodCall/> + <#-- method is provided by a context parameter --> + <#elseif providingParameter??> + <#if static><@includeModel object=providingParameter.type/><#else>${providingParameter.name}.<@methodCall/> <#-- method is referenced java8 static method in the mapper to implement (interface) --> - <#elseif static><@includeModel object=definingType/>.<@params/> + <#elseif static> + <@includeModel object=definingType raw=true/>.<@methodCall/> + <#elseif constructor> + new <@includeModel object=definingType/><#if (parameterBindings?size > 0)>( <@arguments/> )<#else>() + <#elseif methodChaining> + <#list methodsToChain as methodToChain><@includeModel object=methodToChain /><#if methodToChain_has_next>. <#else> - <@params/> + <@methodCall/> - <#macro params> - <@compress> - ${name}<#if (parameters?size > 0)>( <@arguments/> )<#else>() - - - <#macro arguments> - <#list parameters as param> - <#if param.targetType> - <#-- a class is passed on for casting, see @TargetType --> - <@includeModel object=ext.targetType raw=true/>.class - <#elseif param.mappingTarget> - ${ext.targetBeanName}.${ext.targetReadAccessorName}() - <#else> - <@_assignment/> - - <#if param_has_next>, + +<#-- + macro: methodCall + purpose: the actual method call (stuff following the dot) +--> +<#macro methodCall> + <#lt>${name}<#if (parameterBindings?size > 0)>( <@arguments/> )<#else>() + +<#-- + macro: arguments + purpose: the arguments in the method call +--> +<#macro arguments> + <#list parameterBindings as param> + <#if param.targetType> + <#-- a class is passed on for casting, see @TargetType --> + <@includeModel object=inferTypeWhenEnum( ext.targetType ) raw=true/>.class<#t> + <#elseif param.mappingTarget> + <#if ext.targetBeanName??>${ext.targetBeanName}<#else>${param.variableName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> + <#elseif param.mappingContext> + ${param.variableName}<#t> + <#elseif param.sourcePropertyName> + "${ext.sourcePropertyName}"<#t> + <#elseif param.targetPropertyName> + "${ext.targetPropertyName}"<#t> + <#elseif param.sourceRHS??> + <@_assignment assignmentToUse=param.sourceRHS/><#t> + <#elseif assignment??> + <@_assignment assignmentToUse=assignment/><#t> + <#else> + ${param.variableName}<#t> + + <#if param_has_next>, <#t> - <#-- context parameter, e.g. for builtin methods concerning date conversion --> - <#if contextParam??>, ${contextParam} - - <#macro _assignment> - <@includeModel object=assignment - targetBeanName=ext.targetBeanName + <#-- context parameter, e.g. for builtin methods concerning date conversion --> + <#if contextParam??>, ${contextParam}<#t> + +<#-- + macro: assignment + purpose: note: takes its targetType from the singleSourceParameterType +--> +<#macro _assignment assignmentToUse> + <@includeModel object=assignmentToUse + presenceCheck=ext.presenceCheck + targetBeanName=ext.targetBeanName existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName targetType=singleSourceParameterType/> - - + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl new file mode 100644 index 0000000000..68b05d84ed --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" --> +<#if isNegate()>!<@includeModel object=methodReference + presenceCheck=true + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl index e9820d008f..04d385a9dc 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.ftl @@ -1,37 +1,30 @@ <#-- - Copyright 2012-2015 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 --> -<#lt>private <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { - - if ( ${sourceParameter.name} == null ) { - return ${returnType.null}; - } - <#list propertyEntries as entry> - <@includeModel object=entry.type/> ${entry.name} = <#if entry_index == 0>${sourceParameter.name}.${entry.accessorName}()<#else>${propertyEntries[entry_index-1].name}.${entry.accessorName}(); - <#if !entry.type.primitive> - if ( ${entry.name} == null ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.NestedPropertyMappingMethod" --> +<#lt>private <@includeModel object=returnType.typeBound/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { +<#list propertyEntries as entry> + <#if entry.presenceChecker?? > + if ( <@includeModel object=entry.presenceChecker /> ) { return ${returnType.null}; } - <#if !entry_has_next> - return ${entry.name}; + <#if entry_has_next> + <@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.source}; + <#else> + return ${entry.source}; - + } +<#macro throws> + <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> + <#list thrownTypes as exceptionType> + <@includeModel object=exceptionType/> + <#if exceptionType_has_next>, <#t> + + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/NoArgumentConstructor.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/NoArgumentConstructor.ftl new file mode 100644 index 0000000000..e0ae1454b7 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/NoArgumentConstructor.ftl @@ -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 + +--> + <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.NoArgumentConstructor" --> +public ${name}() { + <#list fragments as fragment> + <#nt><@includeModel object=fragment/> + + } diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl index 9464a7dd5b..f45659cb5d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl @@ -1,27 +1,17 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.PropertyMapping" --> <@includeModel object=assignment targetBeanName=ext.targetBeanName existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=targetReadAccessorName targetWriteAccessorName=targetWriteAccessorName + sourcePropertyName=sourcePropertyName + targetPropertyName=name targetType=targetType - defaultValueAssignment=defaultValueAssignment /> + defaultValueAssignment=defaultValueAssignment /> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ServicesEntry.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ServicesEntry.ftl index 007cd60acd..facbb5f8b7 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ServicesEntry.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ServicesEntry.ftl @@ -1,21 +1,9 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.ServicesEntry" --> ${implementationPackage}.${implementationName} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl new file mode 100644 index 0000000000..0f335f9c54 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -0,0 +1,188 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.StreamMappingMethod" --> +<#import "macro/CommonMacros.ftl" as lib> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + +<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { + <#--TODO does it even make sense to do a callback if the result is a Stream, as they are immutable--> + <#list beforeMappingReferencesWithoutMappingTarget as callback> + <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + <#if !callback_has_next> + + + + <#if sourceParameterPresenceCheck??> + if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) { + <#if !mapNullToDefault> + return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null; + <#else> + <#if resultType.arrayType> + <#if existingInstanceMapping> + <#-- we can't clear an existing array, so we've got to clear by setting values to default --> + for (int ${index2Name} = 0; ${index2Name} < ${resultName}.length; ${index2Name}++ ) { + ${resultName}[${index2Name}] = ${resultElementType.null}; + } + return<#if returnType.name != "void"> ${resultName}; + <#else> + return <@lib.constructArrayType targetType=resultType targetSize=0/>; + + <#elseif resultType.iterableType> + <#if existingInstanceMapping> + ${resultName}.clear(); + return<#if returnType.name != "void"> ${resultName}; + <#else> + return <@includeModel object=iterableCreation useSizeIfPossible=false/>; + + <#else> + <#if existingInstanceMapping> + <#-- We cannot update an existing stream so we just return the old one --> + return<#if returnType.name != "void"> ${resultName}; + <#else> + return Stream.empty(); + + + + } + + + <#-- A variable needs to be defined if there are before or after mappings and this is not exisitingInstanceMapping --> + <#assign needVarDefine = (beforeMappingReferencesWithMappingTarget?has_content || afterMappingReferences?has_content) && !existingInstanceMapping /> + + <#if resultType.arrayType> + <#if needVarDefine> + <#assign needVarDefine = false /> + <#-- We create a null array which later will be directly assigned from the stream--> + <@includeModel object=resultElementType/>[] ${resultName} = null; + + <#elseif resultType.iterableType> + <#if existingInstanceMapping> + ${resultName}.clear(); + <#elseif needVarDefine> + <#assign needVarDefine = false /> + <#-- Use the interface type on the left side, except it is java.lang.Iterable; use the implementation type - if present - on the right side --> + <@iterableLocalVarDef/> ${resultName} = <@includeModel object=iterableCreation useSizeIfPossible=true/>; + + <#else> + <#-- Streams are immutable so we can't update them --> + <#if needVarDefine> + <#assign needVarDefine = false /> + <@iterableLocalVarDef/> ${resultName} = Stream.empty(); + + + + <#list beforeMappingReferencesWithMappingTarget as callback> + <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + <#if !callback_has_next> + + + + + <#-- If there are no after mappings, no variable was created before i.e. no before mappings + and this is not an existingInstanceMapping then we can return immediatelly --> + <#assign canReturnImmediatelly = !afterMappingReferences?has_content && !needVarDefine && !existingInstanceMapping/> + + <#if resultType.arrayType> + <#if existingInstanceMapping> + int ${index1Name} = 0; + for ( <@includeModel object=resultElementType/> ${loopVariableName} : ${sourceParameter.name}.limit( ${resultName}.length )<@streamMapSupplier />.toArray( <@includeModel object=resultElementType raw=true/>[]::new ) ) { + if ( ( ${index1Name} >= ${resultName}.length ) ) { + break; + } + ${resultName}[${index1Name}++] = ${loopVariableName}; + } + <#else> + <#if canReturnImmediatelly><#if returnType.name != "void">return <#else> <#if needVarDefine>${resultElementType}[] <#else>${resultName} = ${sourceParameter.name}<@streamMapSupplier /> + .toArray( <@includeModel object=resultElementType raw=true/>[]::new ); + + <#elseif resultType.iterableType> + <#if existingInstanceMapping || !canReturnImmediatelly> + ${resultName}.addAll( ${sourceParameter.name}<@streamMapSupplier /> + .collect( Collectors.toCollection( <@iterableCollectionSupplier /> ) ) + ); + <#else> + <@returnLocalVarDefOrUpdate> + <#lt>${sourceParameter.name}<@streamMapSupplier /> + .collect( Collectors.toCollection( <@iterableCollectionSupplier /> ) ); + + + + <#else> + <#-- Streams are immutable so we can't update them --> + <#if !existingInstanceMapping> + <#--TODO fhr: after the result is no longer the same instance, how does it affect the + Before mapping methods. Does it even make sense to have before mapping on a stream? --> + <#if sourceParameter.type.arrayType> + <@returnLocalVarDefOrUpdate>Stream.of( ${sourceParameter.name} )<@streamMapSupplier />; + <#elseif sourceParameter.type.collectionType> + <@returnLocalVarDefOrUpdate>${sourceParameter.name}.stream()<@streamMapSupplier />; + <#elseif sourceParameter.type.iterableType> + <@returnLocalVarDefOrUpdate>StreamSupport.stream( ${sourceParameter.name}.spliterator(), false )<@streamMapSupplier />; + <#else> + <@returnLocalVarDefOrUpdate>${sourceParameter.name}<@streamMapSupplier />; + + + + + + <#list afterMappingReferences as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + + + <#if !canReturnImmediatelly && returnType.name != "void"> + return ${resultName}; + +} +<#macro throws> + <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> + <#list thrownTypes as exceptionType> + <@includeModel object=exceptionType/> + <#if exceptionType_has_next>, <#t> + + + +<#macro iterableSize> + <@compress single_line=true> + <#if sourceParameter.type.arrayType> + ${sourceParameter.name}.length + <#else> + ${sourceParameter.name}.size() + + + +<#macro iterableLocalVarDef> + <@compress single_line=true> + <#if resultType.fullyQualifiedName == "java.lang.Iterable"> + <@includeModel object=resultType.implementationType/> + <#else> + <@includeModel object=resultType/> + + + +<#macro iterableCollectionSupplier> + <@compress single_line=true> + <#if resultType.implementationType??> + <@includeModel object=resultType.implementationType/> + <#else> + <@includeModel object=resultType/>::new + + +<#macro streamMapSupplier> + <@compress> + <#if !elementAssignment.directAssignment?? || !elementAssignment.directAssignment> + .map( <@includeModel object=elementAssignment targetBeanName=resultName targetType=resultElementType/> ) + + + +<#macro returnLocalVarDefOrUpdate> + <#if canReturnImmediatelly><#if returnType.name != "void">return <#elseif needVarDefine><@iterableLocalVarDef/> ${resultName} = <#else>${resultName} = <#nested /> + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl new file mode 100644 index 0000000000..48e20e7017 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ToOptionalTypeConversion.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.ToOptionalTypeConversion" --> +<@compress single_line=true> +<@includeModel object=targetType.asRawType() />.of( <@_assignment/> ) +<#macro _assignment> + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> + + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl index 5a7519f9e9..a5e5798d1c 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl @@ -1,23 +1,11 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.TypeConversion" --> <@compress single_line=true> ${openExpression}<@_assignment/>${closeExpression} <#macro _assignment> @@ -26,6 +14,8 @@ ${openExpression}<@_assignment/>${closeExpression} existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl new file mode 100644 index 0000000000..224b6c3dfa --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl @@ -0,0 +1,95 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.ValueMappingMethod" --> +<#list annotations as annotation> + <#nt><@includeModel object=annotation/> + +<#if overridden>@Override +<#lt>${accessibility.keyword} <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, )<@throws/> { + <#list beforeMappingReferencesWithoutMappingTarget as callback> + <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + <#if !callback_has_next> + + + + if ( ${sourceParameter.name} == null ) { + <#if nullTarget.targetAsException>throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>return <@writeTarget target=nullTarget.target/>; + } + + <#if versionInformation.isSourceVersionAtLeast14()> + <#if valueMappings.empty> + <#if defaultTarget.targetAsException> + <@includeModel object=resultType/> ${resultName}; + throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} ); + <#else> + <@includeModel object=resultType/> ${resultName} = <@writeTarget target=defaultTarget.target/>; + + <#else> + <@includeModel object=resultType/> ${resultName} = switch ( ${sourceParameter.name} ) { + <#list valueMappings as valueMapping> + case <@writeSource source=valueMapping.source/> -> <#if valueMapping.targetAsException > throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else><@writeTarget target=valueMapping.target/>; + + <#if isDefaultTargetRequired()> + default -> <#if defaultTarget.targetAsException>throw new <@includeModel object=unexpectedValueMappingException/>( "Unexpected enum constant: " + ${sourceParameter.name} )<#else><@writeTarget target=defaultTarget.target/>; + + }; + + <#else> + <@includeModel object=resultType/> ${resultName}; + + switch ( ${sourceParameter.name} ) { + <#list valueMappings as valueMapping> + case <@writeSource source=valueMapping.source/>: <#if valueMapping.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>${resultName} = <@writeTarget target=valueMapping.target/>; + break; + + default: <#if defaultTarget.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <@writeTarget target=defaultTarget.target/>; + } + + <#list beforeMappingReferencesWithMappingTarget as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + + <#list afterMappingReferences as callback> + <#if callback_index = 0> + + + <@includeModel object=callback targetBeanName=resultName targetType=resultType/> + + + <#if !(valueMappings.empty && defaultTarget.targetAsException)> + return ${resultName}; + +} +<#macro writeSource source=""> + <#if sourceParameter.type.enumType> + ${source}<#t> + <#elseif sourceParameter.type.string> + "${source}"<#t> + + +<#macro writeTarget target=""> + <#if target?has_content> + <#if returnType.enumType> + <@includeModel object=returnType/>.${target}<#t> + <#elseif returnType.string> + "${target}"<#t> + + <#else> + null<#t> + + +<#macro throws> + <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> + <#list thrownTypes as exceptionType> + <@includeModel object=exceptionType/> + <#if exceptionType_has_next>, <#t> + + + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl new file mode 100644 index 0000000000..21c1977f58 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/AnnotationElement.ftl @@ -0,0 +1,48 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.annotation.AnnotationElement" --> +<@compress single_line=true> + <#if elementName??> + ${elementName} = + + <#if (values?size > 1) > + { + + <#-- rt and lt tags below are for formatting the arrays so that there are no spaces before ',' --> + <#list values as value> + <#if boolean> + ${value?c}<#rt> + <#elseif byte> + ${value}<#rt> + <#elseif character> + '${value}'<#rt> + <#elseif class> + <@includeModel object=value raw=true/>.class<#rt> + <#elseif double> + ${value?c}<#rt> + <#elseif enum> + <@includeModel object=value/><#rt> + <#elseif float> + ${value?c}f<#rt> + <#elseif integer> + ${value?c}<#rt> + <#elseif long> + ${value?c}L<#rt> + <#elseif short> + ${value?c}<#rt> + <#elseif string> + "${value}"<#rt> + + <#if value_has_next> + , <#lt> + + + <#if (values?size > 1) > + } + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl new file mode 100644 index 0000000000..145b933156 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/annotation/EnumAnnotationElementHolder.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.annotation.EnumAnnotationElementHolder" --> +<@includeModel object=enumClass raw=true/>.${name}<#rt> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/AdderWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/AdderWrapper.ftl index 4d5344002e..9160a62d9e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/AdderWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/AdderWrapper.ftl @@ -1,46 +1,17 @@ <#-- - Copyright 2012-2015 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 --> -<#if (thrownTypes?size == 0) > - for ( <@includeModel object=sourceType/> ${iteratorReference} : ${sourceReference} ) { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> ); - } -<#else> - try { - for ( <@includeModel object=sourceType/> ${iteratorReference} : ${sourceReference} ) { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> ); - } - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - - \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.AdderWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.handleExceptions> + <@lib.sourceLocalVarAssignment/> + <@lib.handleSourceReferenceNullCheck> + for ( <@includeModel object=adderType.typeBound/> ${sourceLoopVarName} : <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} ) { + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>; + } + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.ftl index 81f23ca17d..2ce8736f96 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.ftl @@ -1,42 +1,15 @@ <#-- - Copyright 2012-2015 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 --> -<#if (thrownTypes?size == 0) > - <@includeModel object=ext.targetType/> ${localVarName} = <@_assignment/>; - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( Arrays.copyOf( ${localVarName}, ${localVarName}.length ) ); -<#else> - try { - <@includeModel object=ext.targetType/> ${localVarName} = <@_assignment/>; - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( Arrays.copyOf( ${localVarName}, ${localVarName}.length ) ); - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - - -<#macro _assignment> - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> - \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.ArrayCopyWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.handleExceptions> + <@lib.sourceLocalVarAssignment/> + <@lib.handleSourceReferenceNullCheck> + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite>Arrays.copyOf( ${sourceLocalVarName}, ${sourceLocalVarName}.length ); + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.ftl new file mode 100644 index 0000000000..449a273614 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/EnumConstantWrapper.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.EnumConstantWrapper" --> +${ext.targetType.createReferenceName()}.${assignment} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl new file mode 100644 index 0000000000..02852eec88 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ExistingInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -0,0 +1,51 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.ExistingInstanceSetterWrapperForCollectionsAndMaps" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.sourceLocalVarAssignment/> +<@lib.handleExceptions> + if ( ${ext.targetBeanName}.${ext.targetReadAccessorName} != null ) { + <@lib.handleLocalVarNullCheck needs_explicit_local_var=false> + ${ext.targetBeanName}.${ext.targetReadAccessorName}.clear(); + ${ext.targetBeanName}.${ext.targetReadAccessorName}.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); + + <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && includeElseBranch>else {<#-- the opposite (defaultValueAssignment) case is handeld inside lib.handleLocalVarNullCheck --> + <#if mapNullToClear> + ${ext.targetBeanName}.${ext.targetReadAccessorName}.clear(); + <#else > + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; + + } + + } + else { + <@callTargetWriteAccessor/> + } + +<#-- + assigns the target via the regular target write accessor (usually the setter) +--> +<#macro callTargetWriteAccessor> + <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>; + + <#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && mapNullToDefault>else { + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; + } + + +<#-- + wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf) +--> +<#macro wrapLocalVarInCollectionInitializer><@compress single_line=true> + <#if enumSet> + EnumSet.copyOf( ${nullCheckLocalVarName} ) + <#else> + <@includeModel object=newInstance/>( ${nullCheckLocalVarName} ) + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl index 991565eaeb..f47a37106e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/GetterWrapperForCollectionsAndMaps.ftl @@ -1,51 +1,23 @@ <#-- - Copyright 2012-2015 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 --> -if ( ${ext.targetBeanName}.${ext.targetWriteAccessorName}() != null ) { - <#if ext.existingInstanceMapping> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}().clear(); - - <#if (thrownTypes?size == 0) > - <@_assignmentLine/> - <#else> - try { - <@_assignmentLine/> - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - - +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.GetterWrapperForCollectionsAndMaps" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.sourceLocalVarAssignment/> +if ( ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing /> != null ) { + <@lib.handleExceptions> + <#if ext.existingInstanceMapping && !ignoreMapNull> + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear(); + + <@lib.handleLocalVarNullCheck needs_explicit_local_var=false> + <#if ext.existingInstanceMapping && ignoreMapNull> + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear(); + + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.<#if ext.targetType.collectionType>addAll<#else>putAll( <@lib.handleWithAssignmentOrNullCheckVar/> ); + + } -<#macro _assignmentLine> - <@includeModel object=localVarType/> ${localVarName} = <@_assignment/>; - if ( ${localVarName} != null ) { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}().<#if ext.targetType.collectionType>addAll<#else>putAll( ${localVarName} ); - } - -<#macro _assignment> - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> - diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl new file mode 100644 index 0000000000..4c3b6d4804 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> +<#assign sourceVarName><#if assignment.sourceLocalVarName?? >${assignment.sourceLocalVarName}<#else>${assignment.sourceReference} +<#if (thrownTypes?size == 0) > + <#compress> + <#if directAssignment> + Function.identity() + <#else> + ${sourceVarName} -> <@_assignment/> + +<#else> + <#compress> + ${sourceVarName} -> { + <@lib.handleExceptions> + return <@_assignment/>; + + } + + +<#macro _assignment> + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl index b74fa8374b..163a52896a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl @@ -1,35 +1,19 @@ <#-- - Copyright 2012-2015 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 --> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.LocalVarWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> <#if (thrownTypes?size == 0) > <#if !ext.isTargetDefined?? ><@includeModel object=ext.targetType/> ${ext.targetWriteAccessorName} = <@_assignment/>; <#else> <#if !ext.isTargetDefined?? ><@includeModel object=ext.targetType/> ${ext.targetWriteAccessorName}; - try { + <@lib.handleExceptions> ${ext.targetWriteAccessorName} = <@_assignment/>; - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - + <#macro _assignment> <@includeModel object=assignment @@ -37,5 +21,6 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewCollectionOrMapWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewCollectionOrMapWrapper.ftl deleted file mode 100644 index c843aaa42e..0000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewCollectionOrMapWrapper.ftl +++ /dev/null @@ -1,28 +0,0 @@ -<#-- - - Copyright 2012-2015 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. - ---> -<@compress single_line=true> -new <#if ext.targetType.implementationType??> - <@includeModel object=ext.targetType.implementationType/> -<#else> - <@includeModel object=ext.targetType/> - -( <@includeModel object=assignment targetBeanName=ext.targetBeanName targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName targetType=ext.targetType/> ) - \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.ftl new file mode 100644 index 0000000000..7b9d746767 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMapsWithNullCheck" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.sourceLocalVarAssignment/> +<@lib.handleExceptions> + <@callTargetWriteAccessor/> + +<#-- + assigns the target via the regular target write accessor (usually the setter) +--> +<#macro callTargetWriteAccessor> + <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> + <#if ext.targetType.implementationType??><@includeModel object=ext.targetType.implementationType/><#else><@includeModel object=ext.targetType/> ${instanceVar} = <@includeModel object=newInstance/>(); + ${instanceVar}.<#if ext.targetType.collectionType>addAll<#else>putAll( ${nullCheckLocalVarName} ); + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite>${instanceVar}; + + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.ftl deleted file mode 100644 index 73503c1cca..0000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NullCheckWrapper.ftl +++ /dev/null @@ -1,39 +0,0 @@ -<#-- - - Copyright 2012-2015 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. - ---> -if ( ${sourceReference} != null ) { - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType - defaultValue=ext.defaultValueAssignment/> -} -<#if ext.defaultValueAssignment?? > -else { - <@includeModel object=ext.defaultValueAssignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> -} - \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl new file mode 100644 index 0000000000..7bbc089d7c --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/OptionalGetWrapper.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.OptionalGetWrapper" --> +<@compress single_line=true> +<#if optionalType.optionalBaseType.isPrimitive()> +${assignment}.getAs${optionalType.optionalBaseType.name?cap_first}() +<#else> +${assignment}.get() + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl new file mode 100644 index 0000000000..9917dbacf8 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.ReturnWrapper" --> +return <@_assignment/>; +<#macro _assignment> + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl index 0b8b1cf6b9..c8f18b1c7f 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapper.ftl @@ -1,61 +1,15 @@ <#-- - Copyright 2012-2015 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 --> -<#if (thrownTypes?size == 0) > - <@assignment_w_defaultValue/> -<#else> - try { - <@assignment_w_defaultValue/> - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - - -<#macro _assignment> - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType - defaultValueAssignment=ext.defaultValueAssignment/> - -<#macro _defaultValueAssignment> - <@includeModel object=ext.defaultValueAssignment.assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> - -<#macro assignment_w_defaultValue> - <#if ext.defaultValueAssignment?? > - <#-- if the assignee property is a primitive, defaulValueAssignment will not be set --> - if ( ${sourceReference} != null ) { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@_assignment/> ); - } - else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@_defaultValueAssignment/> ); - } - <#else> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <@_assignment/> ); - - \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.SetterWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.handleExceptions> + <@lib.sourceLocalVarAssignment/> + <@lib.handleSourceReferenceNullCheck> + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>; + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.ftl index 5cac08f93c..beb6bb1f3a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMaps.ftl @@ -1,69 +1,13 @@ <#-- - Copyright 2012-2015 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 --> -<#if ( ext.existingInstanceMapping ) > - if ( ${ext.targetBeanName}.${targetGetterName}() != null ) { - ${ext.targetBeanName}.${targetGetterName}().clear(); - <#if ext.targetType.collectionType> - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName="${targetGetterName}().addAll" - targetType=ext.targetType/> - <#else> - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName="${targetGetterName}().putAll" - targetType=ext.targetType/> - - } - else { - <#if newCollectionOrMapAssignment??> - <@_newCollectionOrMapAssignment/> - <#else> - <@_assignment/> - - } -<#else> - <#if newCollectionOrMapAssignment??> - <@_newCollectionOrMapAssignment/> - <#else> - <@_assignment/> - - -<#macro _assignment> - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> - -<#macro _newCollectionOrMapAssignment> - <@includeModel object=newCollectionOrMapAssignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> - \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMaps" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.sourceLocalVarAssignment/> +<@lib.handleExceptions> + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>; + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl new file mode 100644 index 0000000000..7fad115bc4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/SetterWrapperForCollectionsAndMapsWithNullCheck.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAndMapsWithNullCheck" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.sourceLocalVarAssignment/> +<@lib.handleExceptions> + <@callTargetWriteAccessor/> + +<#-- + assigns the target via the regular target write accessor (usually the setter) +--> +<#macro callTargetWriteAccessor> + <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>; + + +<#-- + wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf) +--> +<#macro wrapLocalVarInCollectionInitializer><@compress single_line=true> + <#if enumSet> + EnumSet.copyOf( ${nullCheckLocalVarName} ) + <#else> + <@includeModel object=newInstance/>( ${nullCheckLocalVarName} ) + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/StreamAdderWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/StreamAdderWrapper.ftl new file mode 100644 index 0000000000..6e7683e05f --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/StreamAdderWrapper.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.StreamAdderWrapper" --> +<#import "../macro/CommonMacros.ftl" as lib> +<@lib.handleExceptions> + <@lib.sourceLocalVarAssignment/> + <@lib.handleSourceReferenceNullCheck> + <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference}.forEach( ${ext.targetBeanName}::${ext.targetWriteAccessorName} ); + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.ftl deleted file mode 100644 index 1f0964d68e..0000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateNullCheckWrapper.ftl +++ /dev/null @@ -1,31 +0,0 @@ -<#-- - - Copyright 2012-2015 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. - ---> -if ( ${sourceReference} != null ) { - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> -} -else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( null ); -} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl index 563e4522ea..933612f579 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl @@ -1,45 +1,49 @@ <#-- - Copyright 2012-2015 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 --> -<#if (thrownTypes?size == 0) > - <@_assignment/>; -<#else> - try { - <@_assignment/>; +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.UpdateWrapper" --> +<#-- @ftlvariable name="ext" type="java.util.Map" --> +<#-- @ftlvariable name="ext.targetType" type="org.mapstruct.ap.internal.model.common.Type" --> +<#import '../macro/CommonMacros.ftl' as lib > +<@lib.handleExceptions> + <#if includeSourceNullCheck> + <@lib.sourceLocalVarAssignment/> + if ( <@handleSourceReferenceNullCheck/> ) { + <@assignToExistingTarget/> + <@lib.handleAssignment/>; } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); + <#if setExplicitlyToDefault || setExplicitlyToNull> + else { + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; } - - -<#macro _assignment> - if ( ${ext.targetBeanName}.${ext.targetReadAccessorName}() == null ) { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}( <#if factoryMethod??><@includeModel object=factoryMethod targetType=ext.targetType/><#else><@_newObject/> ); + + <#else> + <@assignToExistingTarget/> + <@lib.handleAssignment/>; + + +<#-- + target innner check and assignment +--> +<#macro assignToExistingTarget> + if ( ${ext.targetBeanName}.${ext.targetReadAccessorName} == null ) { + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; } - <@includeModel object=assignment - targetBeanName=ext.targetBeanName - existingInstanceMapping=ext.existingInstanceMapping - targetReadAccessorName=ext.targetReadAccessorName - targetWriteAccessorName=ext.targetWriteAccessorName - targetType=ext.targetType/> -<#macro _newObject>new <#if ext.targetType.implementationType??><@includeModel object=ext.targetType.implementationType/><#else><@includeModel object=ext.targetType/>() +<#macro handleSourceReferenceNullCheck> + <@compress single_line=true> + <#if sourcePresenceCheckerReference?? > + <@includeModel object=sourcePresenceCheckerReference + targetPropertyName=ext.targetPropertyName + sourcePropertyName=ext.sourcePropertyName + targetType=ext.targetType/> + <#else> + <#if sourceLocalVarName??> ${sourceLocalVarName} <#else> ${sourceReference} != null + + + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/FinalField.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/FinalField.ftl new file mode 100644 index 0000000000..3d04bc3aec --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/FinalField.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingField" --> +private final <@includeModel object=type/> ${variableName}; \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.ftl new file mode 100644 index 0000000000..6951952487 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NegatePresenceCheck.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.NegatePresenceCheck" --> +!( <@includeModel object=presenceCheck /> ) \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NewInstanceCreation.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NewInstanceCreation.ftl new file mode 100644 index 0000000000..9b78977245 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/NewInstanceCreation.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.NewInstanceCreation" --> +<@compress single_line=true> +new <@includeModel object=rawType/><#if generic><> + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Parameter.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Parameter.ftl index 6be291a254..41afd0a42e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Parameter.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Parameter.ftl @@ -1,21 +1,9 @@ <#-- - Copyright 2012-2015 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 --> -<@includeModel object=type/> ${name} \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.Parameter" --> +<@includeModel object=type asVarArgs=varArgs/> ${name} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/SourceRHS.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/SourceRHS.ftl new file mode 100644 index 0000000000..aaf1eb8df4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/SourceRHS.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.SourceRHS" --> +<#if sourceLoopVarName?? && !ext.presenceCheck??>${sourceLoopVarName}<#elseif sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Type.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Type.ftl index aa79ab93ff..c039dda504 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Type.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/Type.ftl @@ -1,21 +1,18 @@ <#-- - Copyright 2012-2015 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 --> -<#if imported>${name}<#else>${fullyQualifiedName}<#if (!ext.raw?? && typeParameters?size > 0) ><<#list typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > \ No newline at end of file +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.Type" --> +<@compress single_line=true> + <#if hasExtendsBound()> + ? extends <@includeModel object=typeBound /> + <#elseif hasSuperBound()> + ? super <@includeModel object=typeBound /> + <#else> + <#assign createReferenceName = createReferenceName() /> + ${createReferenceName?keep_before("[]")}<#if (!ext.raw?? && typeParameters?size > 0) ><<#list typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, >${createReferenceName?keep_after("[]")}<#if ext.asVarArgs!false>...<#elseif isArrayType()>[] + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl new file mode 100644 index 0000000000..0599e8c0e4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -0,0 +1,207 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- + macro: handleSourceReferenceNullCheck + + purpose: macro surrounds nested with either a source presence checker or a null check. It acts directly on the + source reference. It adds an else clause with the default assigment when applicable. + + requires: caller to implement boolean:getIncludeSourceNullCheck() +--> +<#-- @ftlvariable name="ext" type="java.util.Map" --> +<#-- @ftlvariable name="ext.targetType" type="org.mapstruct.ap.internal.model.common.Type" --> +<#macro handleSourceReferenceNullCheck> + <#if sourcePresenceCheckerReference??> + if ( <@includeModel object=sourcePresenceCheckerReference + targetPropertyName=ext.targetPropertyName + sourcePropertyName=ext.sourcePropertyName + targetType=ext.targetType/> ) { + <#nested> + } + <@elseDefaultAssignment/> + <#elseif includeSourceNullCheck || ext.defaultValueAssignment??> + if ( <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference}<#if sourceType.optionalType>.isPresent()<#else> != null ) { + <#nested> + } + <@elseDefaultAssignment/> + <#else> + <#nested> + + +<#-- + local macro related to handleSourceReferenceNullCheck + note: the <#elseif setExplicitlyToDefault || setExplicitlyToNull> is only relevant for update mappings + the default value takes precedence +--> +<#macro elseDefaultAssignment> + <#if ext.defaultValueAssignment?? > + else { + <@handeDefaultAssigment/> + } + <#elseif setExplicitlyToDefault || setExplicitlyToNull> + else { + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull!false>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; + } + + +<#-- + macro: handleLocalVarNullCheck + + purpose: macro surrounds nested with either a source presence checker or a null check. It always uses + a local variable. Note that the local variable assignemnt is inside the IF statement for the + source presence check. Note also, that the else clause contains the default variable assignment if + present. + + requires: caller to implement String:getNullCheckLocalVarName() + caller to implement Type:getNullCheckLocalVarType() +--> +<#macro handleLocalVarNullCheck needs_explicit_local_var> + <#if sourcePresenceCheckerReference??> + if ( <@includeModel object=sourcePresenceCheckerReference + targetType=ext.targetType + sourcePropertyName=ext.sourcePropertyName + targetPropertyName=ext.targetPropertyName /> ) { + <#if needs_explicit_local_var> + <@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>; + <#nested> + <#else> + <#nested> + + } + <#else> + <@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>; + if ( ${nullCheckLocalVarName} != null ) { + <#nested> + } + + <#if ext.defaultValueAssignment?? > + else { + <@handeDefaultAssigment/> + } + + +<#-- + Gives the value that needs to be assigned. If there is a sourcePresenceCheckerReference then a direct + lib.handleAssignment is done, otherwise nullCheckLocalVarName is used. + + requires: caller to implement String:getNullCheckLocalVarName() +--> +<#macro handleWithAssignmentOrNullCheckVar><#if sourcePresenceCheckerReference??><@lib.handleAssignment/><#else>${nullCheckLocalVarName} +<#-- + macro: handleExceptions + + purpose: Includes the try - catch clauses around the nested code. +--> +<#macro handleExceptions> + <#if (thrownTypes?size == 0) > + <#nested> + <#else> + try { + <#nested> + } + <@compress single_line=true>catch ( + <#list thrownTypes as exceptionType> + <#if exceptionType_index > 0> | + <@includeModel object=exceptionType/> + + e ) { + + + throw new RuntimeException( e ); + } + + +<#-- +Performs a standard assignment. +--> +<#macro handleAssignment> + <@includeModel object=assignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType/> + +<#-- +Performs a default assignment with a default value. +--> +<#macro handeDefaultAssigment> + <@includeModel object=ext.defaultValueAssignment + targetBeanName=ext.targetBeanName + existingInstanceMapping=ext.existingInstanceMapping + targetReadAccessorName=ext.targetReadAccessorName + targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName + targetType=ext.targetType + defaultValue=ext.defaultValue/> + +<#-- + macro: handleWrite + + purpose: To handle the writing to a field or using a method. The line is not closed with ';' +--> +<#macro handleWrite><#if fieldAssignment> = <#nested><#else>( <#nested> ) + +<#-- + macro: handleWriteAccesing + + purpose: To handle accesing the write target type +--> +<#macro handleWriteAccesing><#if fieldAssignment><#else>() +<#-- + macro: initTargetObject + + purpose: To factorize or construct a new target object +--> +<#macro initTargetObject><@compress single_line=true> + <#if factoryMethod??> + <@includeModel object=factoryMethod targetType=ext.targetType/> + <#else> + <@constructTargetObject targetType=ext.targetType/> + + +<#-- +--> +<#macro constructArrayType targetType targetSize><@compress single_line=true> + <#assign targetTypeString><@includeModel object=targetType raw=true/> + new ${targetTypeString?keep_before("[")}[${targetSize}]${targetTypeString?keep_after("]")} + +<#-- + macro: constructTargetObject + + purpose: Either call the constructor of the target object directly or of the implementing type. + Emits the diamond operator for generic types so type-arguments are inferred from the + assignment context. +--> +<#-- @ftlvariable name="targetType" type="org.mapstruct.ap.internal.model.common.Type" --> +<#macro constructTargetObject targetType><@compress single_line=true> + <#if targetType.implementationType??> + new <@includeModel object=targetType.implementationType raw=true/><#if targetType.implementationType.typeParameters?size != 0><>() + <#elseif targetType.arrayType> + <@constructArrayType targetType=targetType targetSize=0/> + <#elseif targetType.sensibleDefault??> + ${targetType.sensibleDefault} + <#elseif targetType.optionalType> + <@includeModel object=targetType.asRawType()/>.of( <@constructTargetObject targetType=targetType.optionalBaseType/> ) + <#else> + new <@includeModel object=targetType raw=true/><#if targetType.typeParameters?size != 0><>() + + +<#-- + macro: sourceLocalVarAssignment + + purpose: assignment for source local variables. The sourceLocalVarName replaces the sourceReference in the + assignmentcall. +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.Assignment" --> +<#macro sourceLocalVarAssignment> + <#if sourceLocalVarName??> + <@includeModel object=sourceType/> ${sourceLocalVarName} = ${sourceReference}; + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.ftl new file mode 100644 index 0000000000..d0c81ff2ce --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AllPresenceChecksPresenceCheck.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.AllPresenceChecksPresenceCheck" --> +<@compress single_line=true> +<#list presenceChecks as presenceCheck> + <#if presenceCheck_index != 0> + && + + <@includeModel object=presenceCheck /> + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl new file mode 100644 index 0000000000..3df10348ad --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/AnyPresenceChecksPresenceCheck.ftl @@ -0,0 +1,16 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.AnyPresenceChecksPresenceCheck" --> +<@compress single_line=true> +<#list presenceChecks as presenceCheck> + <#if presenceCheck_index != 0> + || + + <@includeModel object=presenceCheck /> + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.ftl new file mode 100644 index 0000000000..447fa375cf --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/JavaExpressionPresenceCheck.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.JavaExpressionPresenceCheck" --> +${javaExpression} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.ftl new file mode 100644 index 0000000000..ebd9f12eca --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/NullPresenceCheck.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.NullPresenceCheck" --> +${sourceReference} <#if isNegate()>==<#else>!= null \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl new file mode 100644 index 0000000000..c321ce20ec --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/OptionalPresenceCheck.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.OptionalPresenceCheck" --> +<@compress single_line=true> + <#if isNegate()> + <#if versionInformation.isSourceVersionAtLeast11()> + ${sourceReference}.isEmpty() + <#else> + !${sourceReference}.isPresent() + + <#else> + ${sourceReference}.isPresent() + + \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl new file mode 100644 index 0000000000..8eddad3485 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/presence/SuffixPresenceCheck.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck" --> +<#if isNegate()>!${sourceReference}${suffix} \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.ftl index 8604cc5076..38cf1ad852 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/CalendarToXmlGregorianCalendar.ftl @@ -1,34 +1,17 @@ <#-- - Copyright 2012-2015 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 --> -private XMLGregorianCalendar ${name}( Calendar cal ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("Calendar")/> cal ) { if ( cal == null ) { return null; } - try { - GregorianCalendar gcal = new GregorianCalendar(); - gcal.setTimeInMillis( cal.getTimeInMillis() ); - return DatatypeFactory.newInstance().newXMLGregorianCalendar( gcal ); - } - catch ( DatatypeConfigurationException ex ) { - throw new RuntimeException( ex ); - } -} \ No newline at end of file + <@includeModel object=findType("GregorianCalendar")/> gcal = new <@includeModel object=findType("GregorianCalendar")/>( cal.getTimeZone() ); + gcal.setTimeInMillis( cal.getTimeInMillis() ); + return ${supportingField.variableName}.newXMLGregorianCalendar( gcal ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/CalendarToZonedDateTime.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/CalendarToZonedDateTime.ftl index 0a747b0c19..e97a1fc69f 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/CalendarToZonedDateTime.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/CalendarToZonedDateTime.ftl @@ -1,27 +1,15 @@ <#-- - Copyright 2012-2015 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 --> -private ZonedDateTime ${name}(Calendar cal) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("ZonedDateTime")/> ${name}(<@includeModel object=findType("Calendar")/> cal) { if ( cal == null ) { return null; } - return ZonedDateTime.ofInstant( cal.toInstant(), cal.getTimeZone().toZoneId() ); -} \ No newline at end of file + return <@includeModel object=findType("ZonedDateTime")/>.ofInstant( cal.toInstant(), cal.getTimeZone().toZoneId() ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.ftl index 23a0fe82a5..7488bc9886 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/DateToXmlGregorianCalendar.ftl @@ -1,34 +1,17 @@ <#-- - Copyright 2012-2015 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 --> -private XMLGregorianCalendar ${name}( Date date ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("Date")/> date ) { if ( date == null ) { return null; } - try { - GregorianCalendar c = new GregorianCalendar(); - c.setTime( date ); - return DatatypeFactory.newInstance().newXMLGregorianCalendar( c ); - } - catch ( DatatypeConfigurationException ex ) { - throw new RuntimeException( ex ); - } -} \ No newline at end of file + <@includeModel object=findType("GregorianCalendar")/> c = new <@includeModel object=findType("GregorianCalendar")/>(); + c.setTime( date ); + return ${supportingField.variableName}.newXMLGregorianCalendar( c ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.ftl index 9e18bae22b..078be41af8 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JaxbElemToValue.ftl @@ -1,27 +1,15 @@ <#-- - Copyright 2012-2015 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 --> -private T ${name}( JAXBElement element ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private T ${name}( <@includeModel object=findType("JAXBElement") raw=true/> element ) { if ( element == null ) { return null; } return element.isNil() ? null : element.getValue(); -} \ No newline at end of file +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.ftl new file mode 100644 index 0000000000..aceec1f83e --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaDateTimeToXmlGregorianCalendar.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("DateTime")/> dt ) { + if ( dt == null ) { + return null; + } + return ${supportingField.variableName}.newXMLGregorianCalendar( + dt.getYear(), + dt.getMonthOfYear(), + dt.getDayOfMonth(), + dt.getHourOfDay(), + dt.getMinuteOfHour(), + dt.getSecondOfMinute(), + dt.getMillisOfSecond(), + dt.getZone().getOffset( null ) / 60000 ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.ftl new file mode 100644 index 0000000000..19a9ce0a20 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateTimeToXmlGregorianCalendar.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("org.joda.time.LocalDateTime")/> dt ) { + if ( dt == null ) { + return null; + } + + return ${supportingField.variableName}.newXMLGregorianCalendar( + dt.getYear(), + dt.getMonthOfYear(), + dt.getDayOfMonth(), + dt.getHourOfDay(), + dt.getMinuteOfHour(), + dt.getSecondOfMinute(), + dt.getMillisOfSecond(), + <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.ftl new file mode 100644 index 0000000000..4b9b855fbd --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalDateToXmlGregorianCalendar.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("org.joda.time.LocalDate")/> dt ) { + if ( dt == null ) { + return null; + } + + return ${supportingField.variableName}.newXMLGregorianCalendarDate( + dt.getYear(), + dt.getMonthOfYear(), + dt.getDayOfMonth(), + <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.ftl new file mode 100644 index 0000000000..43a8e67a92 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/JodaLocalTimeToXmlGregorianCalendar.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("org.joda.time.LocalTime")/> dt ) { + if ( dt == null ) { + return null; + } + + return ${supportingField.variableName}.newXMLGregorianCalendarTime( + dt.getHourOfDay(), + dt.getMinuteOfHour(), + dt.getSecondOfMinute(), + dt.getMillisOfSecond(), + <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ); + +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.ftl new file mode 100644 index 0000000000..fb7706b390 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateTimeToXmlGregorianCalendar.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("java.time.LocalDateTime")/> localDateTime ) { + if ( localDateTime == null ) { + return null; + } + + return ${supportingField.variableName}.newXMLGregorianCalendar( + localDateTime.getYear(), + localDateTime.getMonthValue(), + localDateTime.getDayOfMonth(), + localDateTime.getHour(), + localDateTime.getMinute(), + localDateTime.getSecond(), + localDateTime.get( ChronoField.MILLI_OF_SECOND ), + <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.ftl new file mode 100644 index 0000000000..469bbf7508 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/LocalDateToXmlGregorianCalendar.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("java.time.LocalDate")/> localDate ) { + if ( localDate == null ) { + return null; + } + + return ${supportingField.variableName}.newXMLGregorianCalendarDate( + localDate.getYear(), + localDate.getMonthValue(), + localDate.getDayOfMonth(), + <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/NewDatatypeFactoryConstructorFragment.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/NewDatatypeFactoryConstructorFragment.ftl new file mode 100644 index 0000000000..1dcbbe34be --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/NewDatatypeFactoryConstructorFragment.ftl @@ -0,0 +1,14 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingConstructorFragment" --> +try { + ${definingMethod.supportingField.variableName} = <@includeModel object=definingMethod.supportingField.type/>.newInstance(); +} +catch ( <@includeModel object=definingMethod.findType("DatatypeConfigurationException")/> ex ) { + throw new RuntimeException( ex ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.ftl index 8d2a9e2c83..00c178b142 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/StringToXmlGregorianCalendar.ftl @@ -1,43 +1,28 @@ <#-- - Copyright 2012-2015 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 --> -private XMLGregorianCalendar ${name}( String date, String dateFormat ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( String date, String dateFormat ) { if ( date == null ) { return null; } try { if ( dateFormat != null ) { - DateFormat df = new SimpleDateFormat( dateFormat ); - GregorianCalendar c = new GregorianCalendar(); + <@includeModel object=findType("DateFormat")/> df = new <@includeModel object=findType("SimpleDateFormat")/>( dateFormat ); + <@includeModel object=findType("GregorianCalendar")/> c = new <@includeModel object=findType("GregorianCalendar")/>(); c.setTime( df.parse( date ) ); - return DatatypeFactory.newInstance().newXMLGregorianCalendar( c ); + return ${supportingField.variableName}.newXMLGregorianCalendar( c ); } else { - return DatatypeFactory.newInstance().newXMLGregorianCalendar( date ); + return ${supportingField.variableName}.newXMLGregorianCalendar( date ); } } - catch ( DatatypeConfigurationException ex ) { - throw new RuntimeException( ex ); - } - catch ( ParseException ex ) { + catch ( <@includeModel object=findType("ParseException")/> ex ) { throw new RuntimeException( ex ); } -} \ No newline at end of file +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.ftl index c72d6c9d27..cfc4ed2adf 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToCalendar.ftl @@ -1,29 +1,17 @@ <#-- - Copyright 2012-2015 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 --> -private Calendar ${name}( XMLGregorianCalendar xcal ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("Calendar")/> ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal ) { if ( xcal == null ) { return null; } - Calendar cal = Calendar.getInstance(); + <@includeModel object=findType("Calendar")/> cal = <@includeModel object=findType("Calendar")/>.getInstance(); cal.setTimeInMillis( xcal.toGregorianCalendar().getTimeInMillis() ); return cal; -} \ No newline at end of file +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.ftl index 27b1e04651..f4ebaab9ca 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToDate.ftl @@ -1,27 +1,15 @@ <#-- - Copyright 2012-2015 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 --> -private static Date ${name}( XMLGregorianCalendar xcal ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.util.Date")/> ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal ) { if ( xcal == null ) { return null; } return xcal.toGregorianCalendar().getTime(); -} \ No newline at end of file +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.ftl new file mode 100644 index 0000000000..15fa82bc51 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaDateTime.ftl @@ -0,0 +1,83 @@ +<#-- + + Copyright MapStruct Authors. + + Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("DateTime")/> ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal ) { + if ( xcal == null ) { + return null; + } + + if ( xcal.getYear() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMonth() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getDay() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getHour() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMinute() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + ) { + if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMillisecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getTimezone() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("DateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond(), + xcal.getMillisecond(), + <@includeModel object=findType("DateTimeZone")/>.forOffsetMillis( xcal.getTimezone() * 60000 ) + ); + } + else if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMillisecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("DateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond(), + xcal.getMillisecond() + ); + } + else if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getTimezone() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("DateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond(), + <@includeModel object=findType("DateTimeZone")/>.forOffsetMillis( xcal.getTimezone() * 60000 ) + ); + } + else if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("DateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond() + ); + } + else if ( xcal.getTimezone() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("DateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + <@includeModel object=findType("DateTimeZone")/>.forOffsetMillis( xcal.getTimezone() * 60000 ) + ); + } + else { + return new <@includeModel object=findType("DateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute() + ); + } + } + return null; +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.ftl new file mode 100644 index 0000000000..3067f5dd65 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDate.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("org.joda.time.LocalDate")/> ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal ) { + if ( xcal == null ) { + return null; + } + + if ( xcal.getYear() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMonth() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getDay() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("org.joda.time.LocalDate")/>( xcal.getYear(), xcal.getMonth(), xcal.getDay() ); + } + + return null; +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.ftl new file mode 100644 index 0000000000..6ace975194 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalDateTime.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("org.joda.time.LocalDateTime")/> ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal ) { + if ( xcal == null ) { + return null; + } + + if ( xcal.getYear() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMonth() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getDay() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getHour() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMinute() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + ) { + if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMillisecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("org.joda.time.LocalDateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond(), + xcal.getMillisecond() + ); + } + else if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("org.joda.time.LocalDateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond() + ); + } + else { + return new <@includeModel object=findType("org.joda.time.LocalDateTime")/>( xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute() + ); + } + } + return null; +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.ftl new file mode 100644 index 0000000000..392401b3f9 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToJodaLocalTime.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("org.joda.time.LocalTime")/> ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal ) { + if ( xcal == null ) { + return null; + } + + if ( xcal.getHour() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMinute() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMillisecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("org.joda.time.LocalTime")/>( xcal.getHour(), + xcal.getMinute(), + xcal.getSecond(), + xcal.getMillisecond() + ); + } + else if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return new <@includeModel object=findType("org.joda.time.LocalTime")/>( + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond() + ); + } + else { + return new <@includeModel object=findType("org.joda.time.LocalTime")/>( xcal.getHour(), + xcal.getMinute() + ); + } + } + return null; +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.ftl new file mode 100644 index 0000000000..81141a41fb --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDate.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.time.LocalDate")/> ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal ) { + if ( xcal == null ) { + return null; + } + + return <@includeModel object=findType("java.time.LocalDate")/>.of( xcal.getYear(), xcal.getMonth(), xcal.getDay() ); +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.ftl new file mode 100644 index 0000000000..6eef5513a4 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToLocalDateTime.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private static <@includeModel object=findType("java.time.LocalDateTime")/> ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal ) { + if ( xcal == null ) { + return null; + } + + if ( xcal.getYear() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMonth() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getDay() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getHour() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMinute() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + ) { + if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED + && xcal.getMillisecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return <@includeModel object=findType("java.time.LocalDateTime")/>.of( + xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond(), + Duration.ofMillis( xcal.getMillisecond() ).getNano() + ); + } + else if ( xcal.getSecond() != <@includeModel object=findType("DatatypeConstants")/>.FIELD_UNDEFINED ) { + return <@includeModel object=findType("java.time.LocalDateTime")/>.of( + xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute(), + xcal.getSecond() + ); + } + else { + return <@includeModel object=findType("java.time.LocalDateTime")/>.of( + xcal.getYear(), + xcal.getMonth(), + xcal.getDay(), + xcal.getHour(), + xcal.getMinute() + ); + } + } + return null; +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.ftl index a5994a1a11..99c0237eec 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/XmlGregorianCalendarToString.ftl @@ -1,24 +1,12 @@ <#-- - Copyright 2012-2015 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 --> -private String ${name}( XMLGregorianCalendar xcal, String dateFormat ) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private String ${name}( <@includeModel object=findType("XMLGregorianCalendar")/> xcal, String dateFormat ) { if ( xcal == null ) { return null; } @@ -27,8 +15,8 @@ private String ${name}( XMLGregorianCalendar xcal, String dateFormat ) { return xcal.toString(); } else { - Date d = xcal.toGregorianCalendar().getTime(); - SimpleDateFormat sdf = new SimpleDateFormat( dateFormat ); + <@includeModel object=findType("java.util.Date")/> d = xcal.toGregorianCalendar().getTime(); + <@includeModel object=findType("SimpleDateFormat")/> sdf = new <@includeModel object=findType("SimpleDateFormat")/>( dateFormat ); return sdf.format( d ); } -} \ No newline at end of file +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToCalendar.ftl index ce75243873..6a96241e34 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToCalendar.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToCalendar.ftl @@ -1,29 +1,17 @@ <#-- - Copyright 2012-2015 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 --> -private Calendar ${name}(ZonedDateTime dateTime) { +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("Calendar")/> ${name}(<@includeModel object=findType("ZonedDateTime")/> dateTime) { if ( dateTime == null ) { return null; } - Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( dateTime.getZone() ) ); + <@includeModel object=findType("Calendar")/> instance = <@includeModel object=findType("Calendar")/>.getInstance( TimeZone.getTimeZone( dateTime.getZone() ) ); instance.setTimeInMillis( dateTime.toInstant().toEpochMilli() ); return instance; -} \ No newline at end of file +} diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.ftl new file mode 100644 index 0000000000..b187daa957 --- /dev/null +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.ftl @@ -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 + +--> +<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.SupportingMappingMethod" --> +private <@includeModel object=findType("XMLGregorianCalendar")/> ${name}( <@includeModel object=findType("ZonedDateTime")/> zdt ) { + if ( zdt == null ) { + return null; + } + + return ${supportingField.variableName}.newXMLGregorianCalendar( <@includeModel object=findType("GregorianCalendar")/>.from( zdt ) ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java index 8403afe571..ba890f0e87 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java @@ -1,37 +1,27 @@ -/** - * Copyright 2012-2015 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.model.common; -import org.junit.Test; -import org.mapstruct.ap.internal.util.JavaTimeConstants; -import org.mapstruct.ap.internal.util.JodaTimeConstants; -import org.mapstruct.ap.testutil.IssueKey; - +import java.lang.annotation.Annotation; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.List; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVisitor; -import java.lang.annotation.Annotation; -import java.util.List; +import org.junit.jupiter.api.Test; +import org.mapstruct.ap.internal.util.JodaTimeConstants; +import org.mapstruct.ap.testutil.IssueKey; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests for {@link org.mapstruct.ap.internal.model.common.DateFormatValidatorFactory}. @@ -126,25 +116,25 @@ public void testJavaTimeValidator() { Type targetType = typeWithFQN( JAVA_LANG_STRING ); - Type sourceType = typeWithFQN( JavaTimeConstants.ZONED_DATE_TIME_FQN ); + Type sourceType = typeWithFQN( ZonedDateTime.class.getCanonicalName() ); assertInvalidDateFormat( sourceType, targetType ); assertInvalidDateFormat( targetType, sourceType ); assertValidDateFormat( sourceType, targetType ); assertValidDateFormat( targetType, sourceType ); - sourceType = typeWithFQN( JavaTimeConstants.LOCAL_DATE_FQN ); + sourceType = typeWithFQN( LocalDate.class.getCanonicalName() ); assertInvalidDateFormat( sourceType, targetType ); assertInvalidDateFormat( targetType, sourceType ); assertValidDateFormat( sourceType, targetType ); assertValidDateFormat( targetType, sourceType ); - sourceType = typeWithFQN( JavaTimeConstants.LOCAL_DATE_TIME_FQN ); + sourceType = typeWithFQN( LocalDateTime.class.getCanonicalName() ); assertInvalidDateFormat( sourceType, targetType ); assertInvalidDateFormat( targetType, sourceType ); assertValidDateFormat( sourceType, targetType ); assertValidDateFormat( targetType, sourceType ); - sourceType = typeWithFQN( JavaTimeConstants.LOCAL_TIME_FQN ); + sourceType = typeWithFQN( LocalTime.class.getCanonicalName() ); assertInvalidDateFormat( sourceType, targetType ); assertInvalidDateFormat( targetType, sourceType ); assertValidDateFormat( sourceType, targetType ); @@ -168,6 +158,7 @@ private Type typeWithFQN(String fullQualifiedName) { null, null, null, + null, voidTypeMirror, null, null, @@ -181,7 +172,12 @@ private Type typeWithFQN(String fullQualifiedName) { false, false, false, - false ); + false, + new HashMap<>( ), + new HashMap<>( ), + false, + false, false + ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java index af81303c2a..3d12de6e0b 100755 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DefaultConversionContextTest.java @@ -1,29 +1,14 @@ -/** - * Copyright 2012-2015 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.model.common; -import org.junit.Test; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.JavaTimeConstants; -import org.mapstruct.ap.internal.util.Message; -import org.mapstruct.ap.testutil.IssueKey; - +import java.lang.annotation.Annotation; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.List; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; @@ -32,10 +17,12 @@ import javax.lang.model.type.TypeVisitor; import javax.tools.Diagnostic; -import java.lang.annotation.Annotation; -import java.util.List; +import org.junit.jupiter.api.Test; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.testutil.IssueKey; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Testing DefaultConversionContext for dateFormat @@ -75,19 +62,29 @@ public R accept(TypeVisitor v, P p) { @Test public void testInvalidDateFormatValidation() { - Type type = typeWithFQN( JavaTimeConstants.ZONED_DATE_TIME_FQN ); + Type type = typeWithFQN( ZonedDateTime.class.getCanonicalName() ); StatefulMessagerMock statefulMessagerMock = new StatefulMessagerMock(); new DefaultConversionContext( - null, statefulMessagerMock, type, type, "qwertz" ); + null, + statefulMessagerMock, + type, + type, + new FormattingParameters( "qwertz", null, null, null, null, null ) + ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isEqualTo( Diagnostic.Kind.ERROR ); } @Test public void testNullDateFormatValidation() { - Type type = typeWithFQN( JavaTimeConstants.ZONED_DATE_TIME_FQN ); + Type type = typeWithFQN( ZonedDateTime.class.getCanonicalName() ); StatefulMessagerMock statefulMessagerMock = new StatefulMessagerMock(); new DefaultConversionContext( - null, statefulMessagerMock, type, type, null ); + null, + statefulMessagerMock, + type, + type, + new FormattingParameters( null, null, null, null, null, null ) + ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isNull(); } @@ -95,8 +92,12 @@ public void testNullDateFormatValidation() { public void testUnsupportedType() { Type type = typeWithFQN( "java.lang.String" ); StatefulMessagerMock statefulMessagerMock = new StatefulMessagerMock(); - new DefaultConversionContext( - null, statefulMessagerMock, type, type, "qwertz" ); + new DefaultConversionContext( null, + statefulMessagerMock, + type, + type, + new FormattingParameters( "qwertz", null, null, null, null, null ) + ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isNull(); } @@ -105,6 +106,7 @@ private Type typeWithFQN(String fullQualifiedName) { null, null, null, + null, voidTypeMirror, null, null, @@ -118,7 +120,12 @@ private Type typeWithFQN(String fullQualifiedName) { false, false, false, - false ); + false, + new HashMap<>( ), + new HashMap<>( ), + false, + false, false + ); } private static class StatefulMessagerMock implements FormattingMessager { @@ -127,24 +134,36 @@ private static class StatefulMessagerMock implements FormattingMessager { @Override public void printMessage(Message msg, Object... arg) { - lastKindPrinted = msg.getDiagnosticKind(); + throw new UnsupportedOperationException( "Should not be called" ); } @Override public void printMessage(Element e, Message msg, Object... arg) { + throw new UnsupportedOperationException( "Should not be called" ); } @Override public void printMessage(Element e, AnnotationMirror a, Message msg, Object... arg) { + throw new UnsupportedOperationException( "Should not be called" ); } @Override public void printMessage(Element e, AnnotationMirror a, AnnotationValue v, Message msg, Object... arg) { + lastKindPrinted = msg.getDiagnosticKind(); + } + + @Override + public void note(int level, Message msg, Object... args) { + throw new UnsupportedOperationException( "Should not be called" ); } public Diagnostic.Kind getLastKindPrinted() { return lastKindPrinted; } + @Override + public boolean isErroneous() { + return false; + } } } diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/common/TypeFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/TypeFactoryTest.java deleted file mode 100644 index 9e9d7e6e33..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/internal/model/common/TypeFactoryTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.internal.model.common; - -import static org.fest.assertions.Assertions.assertThat; - -import org.junit.Test; - -public class TypeFactoryTest { - - @Test - public void shouldReturnNullIfNoClassNameIsProvided() { - String result = TypeFactory.trimSimpleClassName( null ); - - assertThat( result ).isNull(); - } - - @Test - public void shouldNotModifyClassNameIfNotAnArray() { - String className = "SimpleClass"; - - String result = TypeFactory.trimSimpleClassName( className ); - - assertThat( result ).isEqualTo( className ); - } - - @Test - public void shouldTrimOneDimensionalArray() { - String result = TypeFactory.trimSimpleClassName( "SimpleClass[]" ); - - assertThat( result ).isEqualTo( "SimpleClass" ); - } - - @Test - public void shouldTrimTwoDimensionalArray() { - String result = TypeFactory.trimSimpleClassName( "SimpleClass[][]" ); - - assertThat( result ).isEqualTo( "SimpleClass" ); - } - - @Test - public void shouldTrimMultiDimensionalArray() { - String result = TypeFactory.trimSimpleClassName( "SimpleClass[][][][][]" ); - - assertThat( result ).isEqualTo( "SimpleClass" ); - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/internal/model/source/SelectionParametersTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/source/SelectionParametersTest.java new file mode 100644 index 0000000000..4c6e78e0c1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/internal/model/source/SelectionParametersTest.java @@ -0,0 +1,381 @@ +/* + * 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.model.source; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.NullType; +import javax.lang.model.type.PrimitiveType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.type.TypeVisitor; +import javax.lang.model.type.WildcardType; + +import org.junit.jupiter.api.Test; +import org.mapstruct.ap.internal.util.TypeUtils; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +public class SelectionParametersTest { + + private static class TestTypeMirror implements TypeMirror { + + private final String name; + + private TestTypeMirror(String name) { + this.name = name; + } + + @Override + public TypeKind getKind() { + return null; + } + + @Override + public R accept(TypeVisitor v, P p) { + return null; + } + + @Override + public List getAnnotationMirrors() { + return null; + } + + @Override + public A getAnnotation(Class annotationType) { + return null; + } + + @Override + public A[] getAnnotationsByType(Class annotationType) { + return null; + } + + @Override + public String toString() { + return name; + } + } + + private final TypeUtils typeUtils = new TypeUtils() { + @Override + public boolean isSubtypeErased(TypeMirror t1, TypeMirror t2) { + throw new UnsupportedOperationException( "isSubTypeErased is not supported" ); + } + + @Override + public Element asElement(TypeMirror t) { + throw new UnsupportedOperationException( "asElement is not supported" ); + } + + @Override + public boolean isSameType(TypeMirror t1, TypeMirror t2) { + return t1.toString().equals( t2.toString() ); + } + + @Override + public boolean isSubtype(TypeMirror t1, TypeMirror t2) { + throw new UnsupportedOperationException( "isSubType is not supported" ); + } + + @Override + public boolean isAssignable(TypeMirror t1, TypeMirror t2) { + throw new UnsupportedOperationException( "isAssignable is not supported" ); + } + + @Override + public boolean contains(TypeMirror t1, TypeMirror t2) { + throw new UnsupportedOperationException( "contains is not supported" ); + } + + @Override + public boolean isSubsignature(ExecutableType m1, ExecutableType m2) { + throw new UnsupportedOperationException( "isSubSignature is not supported" ); + } + + @Override + public List directSupertypes(TypeMirror t) { + throw new UnsupportedOperationException( "directSupertypes is not supported" ); + } + + @Override + public TypeMirror erasure(TypeMirror t) { + throw new UnsupportedOperationException( "erasure is not supported" ); + } + + @Override + public TypeElement boxedClass(PrimitiveType p) { + throw new UnsupportedOperationException( "boxedClass is not supported" ); + } + + @Override + public PrimitiveType unboxedType(TypeMirror t) { + throw new UnsupportedOperationException( "unboxedType is not supported" ); + } + + @Override + public TypeMirror capture(TypeMirror t) { + throw new UnsupportedOperationException( "capture is not supported" ); + } + + @Override + public PrimitiveType getPrimitiveType(TypeKind kind) { + throw new UnsupportedOperationException( "getPrimitiveType is not supported" ); + } + + @Override + public NullType getNullType() { + throw new UnsupportedOperationException( "nullType is not supported" ); + } + + @Override + public NoType getNoType(TypeKind kind) { + throw new UnsupportedOperationException( "noType is not supported" ); + } + + @Override + public ArrayType getArrayType(TypeMirror componentType) { + throw new UnsupportedOperationException( "getArrayType is not supported" ); + } + + @Override + public WildcardType getWildcardType(TypeMirror extendsBound, TypeMirror superBound) { + throw new UnsupportedOperationException( "getWildCardType is not supported" ); + } + + @Override + public DeclaredType getDeclaredType(TypeElement typeElem, TypeMirror... typeArgs) { + throw new UnsupportedOperationException( "getDeclaredType is not supported" ); + } + + @Override + public DeclaredType getDeclaredType(DeclaredType containing, TypeElement typeElem, TypeMirror... typeArgs) { + throw new UnsupportedOperationException( "getDeclaredType is not supported" ); + } + + @Override + public TypeMirror asMemberOf(DeclaredType containing, Element element) { + throw new UnsupportedOperationException( "asMemberOf is not supported" ); + } + }; + + @Test + public void testGetters() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + assertThat( params.getResultType() ).isSameAs( resultType ); + assertThat( params.getQualifiers() ).hasSameElementsAs( qualifiers ); + assertThat( params.getQualifyingNames() ).hasSameElementsAs( qualifyingNames ); + } + + @Test + public void testHashCode() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + SelectionParameters params = new SelectionParameters( null, qualifyingNames, resultType, null ); + + int expectedHash = 3 * 97 + qualifyingNames.hashCode(); + expectedHash = 97 * expectedHash + "resultType".hashCode(); + assertThat( params.hashCode() ).as( "Expected HashCode" ).isEqualTo( expectedHash ); + } + + @Test + public void testHashCodeWithAllNulls() { + SelectionParameters params = new SelectionParameters( null, null, null, null ); + + assertThat( params.hashCode() ).as( "All nulls hashCode" ).isEqualTo( 3 * 97 * 97 ); + } + + @Test + public void testHashCodeWithNullQualifyingNames() { + TypeMirror resultType = new TestTypeMirror( "someType" ); + SelectionParameters params = new SelectionParameters( null, null, resultType, null ); + + assertThat( params.hashCode() ) + .as( "QualifyingNames null hashCode" ) + .isEqualTo( 3 * 97 * 97 + "someType".hashCode() ); + } + + @Test + public void testHashCodeWithNullResultType() { + List qualifyingNames = Collections.singletonList( "mapstruct" ); + SelectionParameters params = new SelectionParameters( null, qualifyingNames, null, null ); + + assertThat( params.hashCode() ) + .as( "ResultType nulls hashCode" ) + .isEqualTo( ( 3 * 97 + qualifyingNames.hashCode() ) * 97 ); + } + + @Test + public void testEqualsSameInstance() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + assertThat( params.equals( params ) ).as( "Self equals" ).isTrue(); + } + + @Test + public void testEqualsWitNull() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + assertThat( params.equals( null ) ).as( "Equals with null" ).isFalse(); + } + + @Test + public void testEqualsQualifiersOneNull() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + SelectionParameters params2 = new SelectionParameters( null, qualifyingNames, resultType, typeUtils ); + + assertThat( params.equals( params2 ) ).as( "Second null qualifiers" ).isFalse(); + assertThat( params2.equals( params ) ).as( "First null qualifiers" ).isFalse(); + } + + @Test + public void testEqualsQualifiersInDifferentOrder() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + List qualifiers2 = new ArrayList<>(); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + SelectionParameters params2 = new SelectionParameters( qualifiers2, qualifyingNames, resultType, typeUtils ); + + assertThat( params.equals( params2 ) ).as( "Different order for qualifiers" ).isFalse(); + assertThat( params2.equals( params ) ).as( "Different order for qualifiers" ).isFalse(); + } + + @Test + public void testEqualsQualifyingNamesOneNull() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + List qualifiers2 = new ArrayList<>(); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params2 = new SelectionParameters( qualifiers2, null, resultType, typeUtils ); + + assertThat( params.equals( params2 ) ).as( "Second null qualifyingNames" ).isFalse(); + assertThat( params2.equals( params ) ).as( "First null qualifyingNames" ).isFalse(); + } + + @Test + public void testEqualsQualifyingNamesInDifferentOrder() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + List qualifyingNames2 = Arrays.asList( "german", "language" ); + List qualifiers2 = new ArrayList<>(); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + SelectionParameters params2 = new SelectionParameters( qualifiers2, qualifyingNames2, resultType, typeUtils ); + + assertThat( params.equals( params2 ) ).as( "Different order for qualifyingNames" ).isFalse(); + assertThat( params2.equals( params ) ).as( "Different order for qualifyingNames" ).isFalse(); + } + + @Test + public void testEqualsResultTypeOneNull() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + List qualifyingNames2 = Arrays.asList( "language", "german" ); + List qualifiers2 = new ArrayList<>(); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params2 = new SelectionParameters( qualifiers2, qualifyingNames2, null, typeUtils ); + + assertThat( params.equals( params2 ) ).as( "Second null resultType" ).isFalse(); + assertThat( params2.equals( params ) ).as( "First null resultType" ).isFalse(); + } + + @Test + public void testAllEqual() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + List qualifyingNames2 = Arrays.asList( "language", "german" ); + TypeMirror resultType2 = new TestTypeMirror( "resultType" ); + List qualifiers2 = new ArrayList<>(); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params2 = new SelectionParameters( qualifiers2, qualifyingNames2, resultType2, typeUtils ); + + assertThat( params.equals( params2 ) ).as( "All equal" ).isTrue(); + assertThat( params2.equals( params ) ).as( "All equal" ).isTrue(); + } + + @Test + public void testDifferentResultTypes() { + List qualifyingNames = Arrays.asList( "language", "german" ); + TypeMirror resultType = new TestTypeMirror( "resultType" ); + List qualifiers = new ArrayList<>(); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params = new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); + + List qualifyingNames2 = Arrays.asList( "language", "german" ); + TypeMirror resultType2 = new TestTypeMirror( "otherResultType" ); + List qualifiers2 = new ArrayList<>(); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeType" ) ); + qualifiers2.add( new TestTypeMirror( "org.mapstruct.test.SomeOtherType" ) ); + SelectionParameters params2 = new SelectionParameters( qualifiers2, qualifyingNames2, resultType2, typeUtils ); + + assertThat( params.equals( params2 ) ).as( "Different resultType" ).isFalse(); + assertThat( params2.equals( params ) ).as( "Different resultType" ).isFalse(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java b/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java new file mode 100644 index 0000000000..f3ba8d3a8c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java @@ -0,0 +1,357 @@ +/* + * 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.util; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Ciaran Liedeman + */ +public class NativeTypesTest { + + @Test + public void testIsNumber() { + assertFalse( NativeTypes.isNumber( null ) ); + assertFalse( NativeTypes.isNumber( Object.class ) ); + assertFalse( NativeTypes.isNumber( String.class ) ); + + assertTrue( NativeTypes.isNumber( double.class ) ); + assertTrue( NativeTypes.isNumber( Double.class ) ); + assertTrue( NativeTypes.isNumber( long.class ) ); + assertTrue( NativeTypes.isNumber( Long.class ) ); + assertTrue( NativeTypes.isNumber( BigDecimal.class ) ); + assertTrue( NativeTypes.isNumber( BigInteger.class ) ); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testUnderscorePlacement1() { + assertThat( getLiteral( long.class.getCanonicalName(), "1234_5678_9012_3456L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "999_99_9999L" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "3.14_15F" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0xFF_EC_DE_5EL" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0xCAFE_BABEL" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0x7fff_ffff_ffff_ffffL" ) ).isNotNull(); + assertThat( getLiteral( byte.class.getCanonicalName(), "0b0010_0101" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0b11010010_01101001_10010100_10010010L" ) ) + .isNotNull(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * You can place underscores only between digits; you cannot place underscores in the following places: + *

        + *
      1. At the beginning or end of a number
      2. + *
      3. Adjacent to a decimal point in a floating point literal
      4. + *
      5. Prior to an F or L suffix
      6. + *
      7. In positions where a string of digits is expected
      8. + *
      + * The following examples demonstrate valid and invalid underscore placements (which are highlighted) in numeric + * literals: + */ + @Test + public void testUnderscorePlacement2() { + + // Invalid: cannot put underscores + // adjacent to a decimal point + assertThat( getLiteral( float.class.getCanonicalName(), "3_.1415F" ) ).isNull(); + + // Invalid: cannot put underscores + // adjacent to a decimal point + assertThat( getLiteral( float.class.getCanonicalName(), "3._1415F" ) ).isNull(); + + // Invalid: cannot put underscores + // prior to an L suffix + assertThat( getLiteral( long.class.getCanonicalName(), "999_99_9999_L" ) ).isNull(); + + // OK (decimal literal) + assertThat( getLiteral( int.class.getCanonicalName(), "5_2" ) ).isNotNull(); + + // Invalid: cannot put underscores + // At the end of a literal + assertThat( getLiteral( int.class.getCanonicalName(), "52_" ) ).isNull(); + + // OK (decimal literal) + assertThat( getLiteral( int.class.getCanonicalName(), "5_______2" ) ).isNotNull(); + + // Invalid: cannot put underscores + // in the 0x radix prefix + assertThat( getLiteral( int.class.getCanonicalName(), "0_x52" ) ).isNull(); + + // Invalid: cannot put underscores + // at the beginning of a number + assertThat( getLiteral( int.class.getCanonicalName(), "0x_52" ) ).isNull(); + + // OK (hexadecimal literal) + assertThat( getLiteral( int.class.getCanonicalName(), "0x5_2" ) ).isNotNull(); + + // Invalid: cannot put underscores + // at the end of a number + assertThat( getLiteral( int.class.getCanonicalName(), "0x52_" ) ).isNull(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testIntegerLiteralFromJLS() { + + // largest positive int: dec / octal / int / binary + assertThat( getLiteral( int.class.getCanonicalName(), "2147483647" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "2147483647" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0x7fff_ffff" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0177_7777_7777" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0b0111_1111_1111_1111_1111_1111_1111_1111" ) ) + .isNotNull(); + + // most negative int: dec / octal / int / binary + // NOTE parseInt should be changed to parseUnsignedInt in Java, than the - sign can dissapear (java8) + // and the function will be true to what the compiler shows. + assertThat( getLiteral( int.class.getCanonicalName(), "-2147483648" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0x8000_0000" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0200_0000_0000" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0b1000_0000_0000_0000_0000_0000_0000_0000" ) ) + .isNotNull(); + + // -1 representation int: dec / octal / int / binary + assertThat( getLiteral( int.class.getCanonicalName(), "-1" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0xffff_ffff" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0377_7777_7777" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0b1111_1111_1111_1111_1111_1111_1111_1111" ) ) + .isNotNull(); + + // largest positive long: dec / octal / int / binary + assertThat( getLiteral( long.class.getCanonicalName(), "9223372036854775807L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0x7fff_ffff_ffff_ffffL" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "07_7777_7777_7777_7777_7777L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0b0111_1111_1111_1111_1111_1111_1111_1111_1111_1111_" + + "1111_1111_1111_1111_1111_1111L" ) ).isNotNull(); + // most negative long: dec / octal / int / binary + assertThat( getLiteral( long.class.getCanonicalName(), "-9223372036854775808L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0x8000_0000_0000_0000L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "010_0000_0000_0000_0000_0000L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_" + + "0000_0000_0000_0000_0000_0000L" ) ).isNotNull(); + // -1 representation long: dec / octal / int / binary + assertThat( getLiteral( long.class.getCanonicalName(), "-1L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0xffff_ffff_ffff_ffffL" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "017_7777_7777_7777_7777_7777L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0b1111_1111_1111_1111_1111_1111_1111_1111_1111_1111_" + + "1111_1111_1111_1111_1111_1111L" ) ).isNotNull(); + + // some examples of ints + assertThat( getLiteral( int.class.getCanonicalName(), "0" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "2" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0372" ) ).isNotNull(); + //assertThat( getLiteral( int.class.getCanonicalName(), "0xDada_Cafe" ) ).isNotNull(); java8 + assertThat( getLiteral( int.class.getCanonicalName(), "1996" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0x00_FF__00_FF" ) ).isNotNull(); + + // some examples of longs + assertThat( getLiteral( long.class.getCanonicalName(), "0777l" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0x100000000L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "2_147_483_648L" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0xC0B0L" ) ).isNotNull(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testFloatingPointLiteralFromJLS() { + + // The largest positive finite literal of type float is 3.4028235e38f. + assertThat( getLiteral( float.class.getCanonicalName(), "3.4028235e38f" ) ).isNotNull(); + // The smallest positive finite non-zero literal of type float is 1.40e-45f. + assertThat( getLiteral( float.class.getCanonicalName(), "1.40e-45f" ) ).isNotNull(); + // The largest positive finite literal of type double is 1.7976931348623157e308. + assertThat( getLiteral( double.class.getCanonicalName(), "1.7976931348623157e308" ) ).isNotNull(); + // The smallest positive finite non-zero literal of type double is 4.9e-324 + assertThat( getLiteral( double.class.getCanonicalName(), "4.9e-324" ) ).isNotNull(); + + // some floats + assertThat( getLiteral( float.class.getCanonicalName(), "3.1e1F" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "2.f" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), ".3f" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "0f" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "3.14f" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "6.022137e+23f" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "-3.14f" ) ).isNotNull(); + + // some doubles + assertThat( getLiteral( double.class.getCanonicalName(), "1e1" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "1e+1" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "2." ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), ".3" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "0.0" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "3.14" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "-3.14" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "1e-9D" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "1e137" ) ).isNotNull(); + + // too large (infinitve) + assertThat( getLiteral( float.class.getCanonicalName(), "3.4028235e38f" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "1.7976931348623157e308" ) ).isNotNull(); + + // too large (infinitve) + assertThat( getLiteral( float.class.getCanonicalName(), "3.4028235e39f" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "1.7976931348623159e308" ) ).isNull(); + + // small + assertThat( getLiteral( float.class.getCanonicalName(), "1.40e-45f" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "0x1.0p-149" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9e-324" ) ).isNotNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "0x0.001P-1062d" ) ).isNotNull(); + + // too small + assertThat( getLiteral( float.class.getCanonicalName(), "1.40e-46f" ) ).isNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "0x1.0p-150" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9e-325" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "0x0.001p-1063d" ) ).isNull(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testBooleanLiteralFromJLS() { + assertThat( getLiteral( boolean.class.getCanonicalName(), "true" ) ).isNotNull(); + assertThat( getLiteral( boolean.class.getCanonicalName(), "false" ) ).isNotNull(); + assertThat( getLiteral( boolean.class.getCanonicalName(), "FALSE" ) ).isNull(); + } + + /** + * checkout https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html + * + * The following example shows other ways you can use the underscore in numeric literals: + */ + @Test + public void testCharLiteralFromJLS() { + + assertThat( getLiteral( char.class.getCanonicalName(), "'a'" ) ).isNotNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'%'" ) ).isNotNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'\t'" ) ).isNotNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'\\'" ) ).isNotNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'\''" ) ).isNotNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'\u03a9'" ) ).isNotNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'\uFFFF'" ) ).isNotNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'\177'" ) ).isNotNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'Ω'" ) ).isNotNull(); + + } + + @Test + public void testShortAndByte() { + assertThat( getLiteral( short.class.getCanonicalName(), "0xFE" ) ).isNotNull(); + + // some examples of ints + assertThat( getLiteral( byte.class.getCanonicalName(), "0" ) ).isNotNull(); + assertThat( getLiteral( byte.class.getCanonicalName(), "2" ) ).isNotNull(); + assertThat( getLiteral( byte.class.getCanonicalName(), "127" ) ).isNotNull(); + assertThat( getLiteral( byte.class.getCanonicalName(), "-128" ) ).isNotNull(); + assertThat( getLiteral( short.class.getCanonicalName(), "1996" ) ).isNotNull(); + assertThat( getLiteral( short.class.getCanonicalName(), "-1996" ) ).isNotNull(); + } + + @Test + public void testMiscellaneousErroneousPatterns() { + assertThat( getLiteral( int.class.getCanonicalName(), "1F" ) ).isNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "1D" ) ).isNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "_1" ) ).isNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "1_" ) ).isNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0x_1" ) ).isNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "0_x1" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9e_-3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9_e-3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4._9e-3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4_.9e-3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "_4.9e-3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9E-3_" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9E_-3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9E-_3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9E+-3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9E+_3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "4.9_E-3" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "0x0.001_P-10d" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "0x0.001P_-10d" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "0x0.001_p-10d" ) ).isNull(); + assertThat( getLiteral( double.class.getCanonicalName(), "0x0.001p_-10d" ) ).isNull(); + } + + @Test + public void testNegatives() { + assertThat( getLiteral( int.class.getCanonicalName(), "-0xffaa" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "-0377_7777" ) ).isNotNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "-0b1111_1111" ) ).isNotNull(); + } + + @Test + public void testFaultyChar() { + assertThat( getLiteral( char.class.getCanonicalName(), "''" ) ).isNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'a" ) ).isNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'aa" ) ).isNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "a'" ) ).isNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "aa'" ) ).isNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "'" ) ).isNull(); + assertThat( getLiteral( char.class.getCanonicalName(), "a" ) ).isNull(); + } + + @Test + public void testFloatWithLongLiteral() { + assertThat( getLiteral( float.class.getCanonicalName(), "156L" ) ).isNotNull(); + assertThat( getLiteral( float.class.getCanonicalName(), "156l" ) ).isNotNull(); + } + + @Test + public void testLongPrimitivesWithLongSuffix() { + assertThat( getLiteral( long.class.getCanonicalName(), "156l" ) ).isNotNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "156L" ) ).isNotNull(); + } + + @Test + public void testIntPrimitiveWithLongSuffix() { + assertThat( getLiteral( int.class.getCanonicalName(), "156l" ) ).isNull(); + assertThat( getLiteral( int.class.getCanonicalName(), "156L" ) ).isNull(); + } + + @Test + public void testTooBigIntegersAndBigLongs() { + assertThat( getLiteral( int.class.getCanonicalName(), "0xFFFF_FFFF_FFFF" ) ).isNull(); + assertThat( getLiteral( long.class.getCanonicalName(), "0xFFFF_FFFF_FFFF_FFFF_FFFF" ) ).isNull(); + } + + @Test + public void testNonSupportedPrimitiveType() { + assertThat( getLiteral( void.class.getCanonicalName(), "0xFFFF_FFFF_FFFF" ) ).isNull(); + } + + private static Class getLiteral(String className, String literal) { + try { + return NativeTypes.getLiteral( className, literal ); + } + catch ( IllegalArgumentException ex ) { + return null; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java b/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java new file mode 100644 index 0000000000..20c7a766a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java @@ -0,0 +1,155 @@ +/* + * 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.util; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.DefaultLocale; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +public class StringsTest { + + @Test + public void testCapitalize() { + assertThat( Strings.capitalize( null ) ).isNull(); + assertThat( Strings.capitalize( "c" ) ).isEqualTo( "C" ); + assertThat( Strings.capitalize( "capitalize" ) ).isEqualTo( "Capitalize" ); + assertThat( Strings.capitalize( "AlreadyCapitalized" ) ).isEqualTo( "AlreadyCapitalized" ); + assertThat( Strings.capitalize( "notCapitalized" ) ).isEqualTo( "NotCapitalized" ); + } + + @Test + public void testDecapitalize() { + assertThat( Strings.decapitalize( null ) ).isNull(); + assertThat( Strings.decapitalize( "c" ) ).isEqualTo( "c" ); + assertThat( Strings.decapitalize( "capitalize" ) ).isEqualTo( "capitalize" ); + assertThat( Strings.decapitalize( "AlreadyCapitalized" ) ).isEqualTo( "alreadyCapitalized" ); + assertThat( Strings.decapitalize( "notCapitalized" ) ).isEqualTo( "notCapitalized" ); + } + + @Test + public void testJoin() { + assertThat( Strings.join( new ArrayList(), "-" ) ).isEqualTo( "" ); + assertThat( Strings.join( Arrays.asList( "Hello", "World" ), "-" ) ).isEqualTo( "Hello-World" ); + assertThat( Strings.join( Arrays.asList( "Hello" ), "-" ) ).isEqualTo( "Hello" ); + } + + @Test + public void testJoinAndCamelize() { + assertThat( Strings.joinAndCamelize( new ArrayList() ) ).isEqualTo( "" ); + assertThat( Strings.joinAndCamelize( Arrays.asList( "Hello", "World" ) ) ).isEqualTo( "HelloWorld" ); + assertThat( Strings.joinAndCamelize( Arrays.asList( "Hello", "world" ) ) ).isEqualTo( "HelloWorld" ); + assertThat( Strings.joinAndCamelize( Arrays.asList( "hello", "world" ) ) ).isEqualTo( "helloWorld" ); + } + + @Test + public void testIsEmpty() { + assertThat( Strings.isEmpty( null ) ).isTrue(); + assertThat( Strings.isEmpty( "" ) ).isTrue(); + assertThat( Strings.isEmpty( " " ) ).isFalse(); + assertThat( Strings.isEmpty( "not empty" ) ).isFalse(); + } + + @Test + public void testGetSaveVariableNameWithArrayExistingVariables() { + assertThat( Strings.getSafeVariableName( "int[]" ) ).isEqualTo( "intArray" ); + assertThat( Strings.getSafeVariableName( "Extends" ) ).isEqualTo( "extends1" ); + assertThat( Strings.getSafeVariableName( "class" ) ).isEqualTo( "class1" ); + assertThat( Strings.getSafeVariableName( "Class" ) ).isEqualTo( "class1" ); + assertThat( Strings.getSafeVariableName( "Case" ) ).isEqualTo( "case1" ); + assertThat( Strings.getSafeVariableName( "Synchronized" ) ).isEqualTo( "synchronized1" ); + assertThat( Strings.getSafeVariableName( "prop", "prop", "prop_" ) ).isEqualTo( "prop1" ); + assertThat( Strings.getSafeVariableName( "_Test" ) ).isEqualTo( "test" ); + assertThat( Strings.getSafeVariableName( "__Test" ) ).isEqualTo( "test" ); + assertThat( Strings.getSafeVariableName( "_0Test" ) ).isEqualTo( "test" ); + assertThat( Strings.getSafeVariableName( "_0123Test" ) ).isEqualTo( "test" ); + assertThat( Strings.getSafeVariableName( "bad/test" ) ).isEqualTo( "bad_test" ); + } + + @Test + public void testGetSaveVariableNameVariablesEndingOnNumberVariables() { + assertThat( Strings.getSafeVariableName( "prop1", "prop1" ) ).isEqualTo( "prop1_1" ); + assertThat( Strings.getSafeVariableName( "prop1", "prop1", "prop1_1" ) ).isEqualTo( "prop1_2" ); + } + + @Test + public void testGetSaveVariableNameWithCollection() { + assertThat( Strings.getSafeVariableName( "int[]", new ArrayList<>() ) ).isEqualTo( "intArray" ); + assertThat( Strings.getSafeVariableName( "Extends", new ArrayList<>() ) ).isEqualTo( "extends1" ); + assertThat( Strings.getSafeVariableName( "prop", Arrays.asList( "prop", "prop1" ) ) ).isEqualTo( "prop2" ); + assertThat( Strings.getSafeVariableName( "prop.font", Arrays.asList( "propFont", "propFont_" ) ) ) + .isEqualTo( "propFont1" ); + assertThat( Strings.getSafeVariableName( "_Test", new ArrayList<>() ) ).isEqualTo( "test" ); + assertThat( Strings.getSafeVariableName( "__Test", Arrays.asList( "test" ) ) ).isEqualTo( "test1" ); + assertThat( Strings.getSafeVariableName( "___", new ArrayList<>() ) ).isEqualTo( "___" ); + assertThat( Strings.getSafeVariableName( "_0Test", new ArrayList<>() ) ).isEqualTo( "test" ); + assertThat( Strings.getSafeVariableName( "__0Test", Arrays.asList( "test" ) ) ).isEqualTo( "test1" ); + assertThat( Strings.getSafeVariableName( "___0", new ArrayList<>() ) ).isEqualTo( "___0" ); + assertThat( Strings.getSafeVariableName( "__0123456789Test", Arrays.asList( "test" ) ) ).isEqualTo( "test1" ); + assertThat( Strings.getSafeVariableName( "bad/test", Arrays.asList( "bad_test" ) ) ).isEqualTo( "bad_test1" ); + } + + @Test + public void testSanitizeIdentifierName() { + assertThat( Strings.sanitizeIdentifierName( "test" ) ).isEqualTo( "test" ); + assertThat( Strings.sanitizeIdentifierName( "int[]" ) ).isEqualTo( "intArray" ); + assertThat( Strings.sanitizeIdentifierName( "_Test" ) ).isEqualTo( "Test" ); + assertThat( Strings.sanitizeIdentifierName( "_int[]" ) ).isEqualTo( "intArray" ); + assertThat( Strings.sanitizeIdentifierName( "__int[]" ) ).isEqualTo( "intArray" ); + assertThat( Strings.sanitizeIdentifierName( "test_" ) ).isEqualTo( "test_" ); + assertThat( Strings.sanitizeIdentifierName( "___" ) ).isEqualTo( "___" ); + assertThat( Strings.sanitizeIdentifierName( "_0Test" ) ).isEqualTo( "Test" ); + assertThat( Strings.sanitizeIdentifierName( "_0123456789Test" ) ).isEqualTo( "Test" ); + assertThat( Strings.sanitizeIdentifierName( "_0int[]" ) ).isEqualTo( "intArray" ); + assertThat( Strings.sanitizeIdentifierName( "__0int[]" ) ).isEqualTo( "intArray" ); + assertThat( Strings.sanitizeIdentifierName( "___0" ) ).isEqualTo( "___0" ); + assertThat( Strings.sanitizeIdentifierName( "bad/test" ) ).isEqualTo( "bad_test" ); + } + + @Test + public void findMostSimilarWord() { + String mostSimilarWord = Strings.getMostSimilarWord( + "fulName", + Arrays.asList( "fullAge", "fullName", "address", "status" ) + ); + assertThat( mostSimilarWord ).isEqualTo( "fullName" ); + } + + @Test + @DefaultLocale("en") + public void capitalizeEnglish() { + String international = Strings.capitalize( "international" ); + assertThat( international ).isEqualTo( "International" ); + } + + @Test + @DefaultLocale("en") + public void decapitalizeEnglish() { + String international = Strings.decapitalize( "International" ); + assertThat( international ).isEqualTo( "international" ); + } + + @Test + @DefaultLocale("tr") + public void capitalizeTurkish() { + String international = Strings.capitalize( "international" ); + assertThat( international ).isEqualTo( "International" ); + } + + @Test + @DefaultLocale("tr") + public void decapitalizeTurkish() { + String international = Strings.decapitalize( "International" ); + assertThat( international ).isEqualTo( "international" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/spi/util/IntrospectorUtilsTest.java b/processor/src/test/java/org/mapstruct/ap/spi/util/IntrospectorUtilsTest.java new file mode 100644 index 0000000000..84469f3f64 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/spi/util/IntrospectorUtilsTest.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.ap.spi.util; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Saheb Preet Singh + */ +public class IntrospectorUtilsTest { + + @Test + public void testDecapitalize() { + assertThat( IntrospectorUtils.decapitalize( null ) ).isNull(); + assertThat( IntrospectorUtils.decapitalize( "" ) ).isEqualTo( "" ); + assertThat( IntrospectorUtils.decapitalize( "URL" ) ).isEqualTo( "URL" ); + assertThat( IntrospectorUtils.decapitalize( "FooBar" ) ).isEqualTo( "fooBar" ); + assertThat( IntrospectorUtils.decapitalize( "PArtialCapitalized" ) ).isEqualTo( "PArtialCapitalized" ); + assertThat( IntrospectorUtils.decapitalize( "notCapitalized" ) ).isEqualTo( "notCapitalized" ); + assertThat( IntrospectorUtils.decapitalize( "a" ) ).isEqualTo( "a" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/NoProperties.java b/processor/src/test/java/org/mapstruct/ap/test/NoProperties.java new file mode 100644 index 0000000000..20efe22568 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/NoProperties.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.ap.test; + +/** + * @author Filip Hrisafov + */ +public class NoProperties { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/WithProperties.java b/processor/src/test/java/org/mapstruct/ap/test/WithProperties.java new file mode 100644 index 0000000000..2a13360e24 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/WithProperties.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.ap.test; + +/** + * @author Filip Hrisafov + */ +public class WithProperties { + + private String string; + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractBaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractBaseMapper.java index fc54c098b7..87eb21b8ab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractBaseMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractBaseMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.abstractclass; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java index 3ea86580b3..4e931b487b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractClassTest.java @@ -1,30 +1,15 @@ -/** - * Copyright 2012-2015 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.abstractclass; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Test for the generation of implementation of abstract base classes. @@ -41,12 +26,12 @@ Identifiable.class, HasId.class, AlsoHasId.class, - Measurable.class + Measurable.class, + Holder.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class AbstractClassTest { - @Test + @ProcessorTest @IssueKey("64") public void shouldCreateImplementationOfAbstractMethod() { Source source = new Source(); @@ -54,7 +39,7 @@ public void shouldCreateImplementationOfAbstractMethod() { assertResult( SourceTargetMapper.INSTANCE.sourceToTarget( source ) ); } - @Test + @ProcessorTest @IssueKey("165") public void shouldCreateImplementationOfMethodFromSuper() { Source source = new Source(); @@ -62,7 +47,7 @@ public void shouldCreateImplementationOfMethodFromSuper() { assertResult( SourceTargetMapper.INSTANCE.sourceToTargetFromBaseMapper( source ) ); } - @Test + @ProcessorTest @IssueKey("165") public void shouldCreateImplementationOfMethodFromInterface() { Source source = new Source(); @@ -73,9 +58,11 @@ public void shouldCreateImplementationOfMethodFromInterface() { private void assertResult(Target target) { assertThat( target ).isNotNull(); assertThat( target.getSize() ).isEqualTo( Long.valueOf( 181 ) ); + assertThat( target.publicSize ).isEqualTo( 191 ); assertThat( target.getBirthday() ).isEqualTo( "Birthday: 26.04.1948" ); assertThat( target.getManuallyConverted() ).isEqualTo( 42 ); assertThat( target.isNotAttractingEqualsMethod() ).isTrue(); assertThat( target.getId() ).isEqualTo( 42L ); + assertThat( target.publicId ).isEqualTo( 52L ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractDto.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractDto.java index 67027cc399..46af734731 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.abstractclass; @@ -22,6 +9,10 @@ * @author Andreas Gudian */ public class AbstractDto implements Identifiable { + + //CHECKSTYLE:OFF + public Long publicId; + //CHECKSTYLE:ON private Long id = 1L; @Override diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractReferencedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractReferencedMapper.java index 5c8241b301..8fd8ab0ef4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractReferencedMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AbstractReferencedMapper.java @@ -1,40 +1,22 @@ -/** - * Copyright 2012-2015 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.abstractclass; -import javax.xml.ws.Holder; - /** * @author Andreas Gudian */ public abstract class AbstractReferencedMapper implements ReferencedMapperInterface { + @Override public int holderToInt(Holder holder) { return 41; } public boolean objectToBoolean(Object obj) { - if ( obj instanceof String ) { - return true; - } - - return false; + return obj instanceof String; } @Override diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AlsoHasId.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AlsoHasId.java index e0b2d575dd..1cb76008c7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AlsoHasId.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/AlsoHasId.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.abstractclass; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/BaseMapperInterface.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/BaseMapperInterface.java index 8865b09e1c..0c9e1ef445 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/BaseMapperInterface.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/BaseMapperInterface.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.abstractclass; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/HasId.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/HasId.java index 5ac6a552b7..d611b229ee 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/HasId.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/HasId.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.abstractclass; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Holder.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Holder.java new file mode 100644 index 0000000000..e05e062464 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Holder.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.ap.test.abstractclass; + +/** + * @author Gunnar Morling + */ +public class Holder { + + public Holder(String string) { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Identifiable.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Identifiable.java index 84cfe3ff45..57561049de 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Identifiable.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Identifiable.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.abstractclass; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Measurable.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Measurable.java index 8766668839..b8351b036f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Measurable.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Measurable.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.abstractclass; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/ReferencedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/ReferencedMapper.java index d3ef9d72bf..c2a01d3f8e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/ReferencedMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/ReferencedMapper.java @@ -1,29 +1,15 @@ -/** - * Copyright 2012-2015 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.abstractclass; -import javax.xml.ws.Holder; - /** * @author Andreas Gudian */ public class ReferencedMapper extends AbstractReferencedMapper { + @Override public int holderToInt(Holder holder) { return 42; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/ReferencedMapperInterface.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/ReferencedMapperInterface.java index 307b8e036b..5d4e3d8661 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/ReferencedMapperInterface.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/ReferencedMapperInterface.java @@ -1,28 +1,14 @@ -/** - * Copyright 2012-2015 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.abstractclass; -import javax.xml.ws.Holder; - /** * @author Andreas Gudian */ public interface ReferencedMapperInterface { + int holderToInt(Holder holder); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java index d7053c88f2..703bc7daf1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java @@ -1,40 +1,30 @@ -/** - * Copyright 2012-2015 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.abstractclass; import java.util.Calendar; -import javax.xml.ws.Holder; - public class Source extends AbstractDto implements HasId, AlsoHasId { + //CHECKSTYLE:OFF + public final int publicSize; + //CHECKSTYLE:ON private final int size; private final Calendar birthday; private final String notAttractingEqualsMethod = "no way"; - private final Holder manuallyConverted = new Holder( "What is the answer?" ); + private final Holder manuallyConverted = new Holder<>( "What is the answer?" ); public Source() { + publicSize = 191; size = 181; birthday = Calendar.getInstance(); birthday.set( 1948, 3, 26 ); super.setId( 42L ); + this.publicId = 52L; } public int getSize() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/SourceTargetMapper.java index 6d9631c0b5..0f8227b3f0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.abstractclass; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java index c77ad4eb90..fef4406ebc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Target.java @@ -1,25 +1,16 @@ -/** - * Copyright 2012-2015 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.abstractclass; public class Target extends AbstractDto { + //CHECKSTYLE:OFF + public Long publicSize; + //CHECKSTYLE:OFF + private Long size; private String birthday; private boolean notAttractingEqualsMethod; diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AbstractAnimal.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AbstractAnimal.java new file mode 100644 index 0000000000..6a82bcc727 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AbstractAnimal.java @@ -0,0 +1,14 @@ +/* + * 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.test.abstractclass.generics; + +/** + * @author Andreas Gudian + */ +public abstract class AbstractAnimal + implements Identifiable, IAnimal { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AbstractHuman.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AbstractHuman.java new file mode 100644 index 0000000000..2e38329f1a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AbstractHuman.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.ap.test.abstractclass.generics; + +public abstract class AbstractHuman + implements GenericIdentifiable, Identifiable { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AnimalKey.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AnimalKey.java new file mode 100644 index 0000000000..ee95bb8884 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/AnimalKey.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.ap.test.abstractclass.generics; + +/** + * @author Andreas Gudian + * + */ +public class AnimalKey extends KeyOfAllBeings { + private boolean typeParameterIsResolvedToAnimalKey; + + public boolean typeParameterIsResolvedToAnimalKey() { + return typeParameterIsResolvedToAnimalKey; + } + + public void setTypeParameterIsResolvedToAnimalKey(boolean typeParameterIsResolvedToAnimalKey) { + this.typeParameterIsResolvedToAnimalKey = typeParameterIsResolvedToAnimalKey; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Child.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Child.java new file mode 100644 index 0000000000..f1f9034def --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Child.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.ap.test.abstractclass.generics; + +/** + * @author Andreas Gudian + * + */ +public class Child extends AbstractHuman { + private KeyOfAllBeings key; + + public KeyOfAllBeings getKey() { + return key; + } + + public void setKey(KeyOfAllBeings key) { + this.key = key; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Elephant.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Elephant.java new file mode 100644 index 0000000000..9d0b37723a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Elephant.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.ap.test.abstractclass.generics; + +/** + * @author Andreas Gudian + * + */ +public class Elephant extends AbstractAnimal { + private AnimalKey key; + + @Override + public AnimalKey getKey() { + return key; + } + + @Override + public void setKey(AnimalKey key) { + this.key = key; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericIdentifiable.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericIdentifiable.java new file mode 100644 index 0000000000..a4107ce86e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericIdentifiable.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.ap.test.abstractclass.generics; + +/** + * @author Andreas Gudian + */ +public interface GenericIdentifiable { + X getKey(); + + void setKey(X id); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericsHierarchyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericsHierarchyMapper.java new file mode 100644 index 0000000000..d002b1304b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericsHierarchyMapper.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.ap.test.abstractclass.generics; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + * + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public abstract class GenericsHierarchyMapper { + public static final GenericsHierarchyMapper INSTANCE = Mappers.getMapper( GenericsHierarchyMapper.class ); + + @Mapping(target = "animalKey", source = "key") + public abstract Target toTarget(AbstractAnimal source); + + @Mapping(target = "keyOfAllBeings", source = "key") + public abstract Target toTarget(AbstractHuman source); + + @Mapping(target = "key", source = "animalKey") + public abstract void updateSourceWithAnimalKey(Target target, @MappingTarget AbstractAnimal bean); + + @Mapping(target = "key", source = "keyOfAllBeings") + public abstract void updateSourceWithKeyOfAllBeings(Target target, @MappingTarget AbstractHuman bean); + + protected AnimalKey modifyAnimalKey(AnimalKey item) { + item.setTypeParameterIsResolvedToAnimalKey( true ); + return item; + } + + protected KeyOfAllBeings modifyKeyOfAllBeings(KeyOfAllBeings item) { + item.setTypeParameterIsResolvedToKeyOfAllBeings( true ); + return item; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericsHierarchyTest.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericsHierarchyTest.java new file mode 100644 index 0000000000..fa483c9037 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/GenericsHierarchyTest.java @@ -0,0 +1,88 @@ +/* + * 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.test.abstractclass.generics; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Andreas Gudian + * + */ +@IssueKey("644,687,688") +@WithClasses({ + AbstractAnimal.class, + AbstractHuman.class, + GenericsHierarchyMapper.class, + Key.class, + KeyOfAllBeings.class, + AnimalKey.class, + Identifiable.class, + GenericIdentifiable.class, + IAnimal.class, + Target.class +}) +public class GenericsHierarchyTest { + + // Running only with the JDK compiler due to a bug in the Eclipse compiler + // (https://bugs.eclipse.org/bugs/show_bug.cgi?id=540101) + // See https://github.com/mapstruct/mapstruct/issues/1553 and https://github.com/mapstruct/mapstruct/pull/1587 + // for more information + @ProcessorTest(Compiler.JDK) + public void determinesAnimalKeyGetter() { + AbstractAnimal source = new Elephant(); + + source.setKey( new AnimalKey() ); + // make sure the jdk compiler resolves the same as we expect + source.getKey().setTypeParameterIsResolvedToAnimalKey( false ); + + Target target = GenericsHierarchyMapper.INSTANCE.toTarget( source ); + + assertThat( target.getAnimalKey().typeParameterIsResolvedToAnimalKey() ).isTrue(); + assertThat( target.getAnimalKey().typeParameterIsResolvedToKeyOfAllBeings() ).isFalse(); + } + + @ProcessorTest + public void determinesKeyOfAllBeingsGetter() { + AbstractHuman source = new Child(); + + source.setKey( new KeyOfAllBeings() ); + // make sure the jdk compiler resolves the same as we expect + source.getKey().setTypeParameterIsResolvedToKeyOfAllBeings( false ); + + Target target = GenericsHierarchyMapper.INSTANCE.toTarget( source ); + + assertThat( target.getKeyOfAllBeings().typeParameterIsResolvedToKeyOfAllBeings() ).isTrue(); + } + + @ProcessorTest + public void determinesItemCSourceSetter() { + Target target = new Target(); + + target.setAnimalKey( new AnimalKey() ); + + Elephant source = new Elephant(); + GenericsHierarchyMapper.INSTANCE.updateSourceWithAnimalKey( target, source ); + + assertThat( source.getKey().typeParameterIsResolvedToAnimalKey() ).isTrue(); + } + + @ProcessorTest + public void determinesItemBSourceSetter() { + Target target = new Target(); + + target.setKeyOfAllBeings( new KeyOfAllBeings() ); + + Child source = new Child(); + GenericsHierarchyMapper.INSTANCE.updateSourceWithKeyOfAllBeings( target, source ); + + assertThat( source.getKey().typeParameterIsResolvedToKeyOfAllBeings() ).isTrue(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/IAnimal.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/IAnimal.java new file mode 100644 index 0000000000..6b96c829d6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/IAnimal.java @@ -0,0 +1,16 @@ +/* + * 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.test.abstractclass.generics; + +/** + * @author Andreas Gudian + */ +public interface IAnimal extends Identifiable { + @Override + ID getKey(); + + void setKey(ID item); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Identifiable.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Identifiable.java new file mode 100644 index 0000000000..4f3d7ca4a7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Identifiable.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.ap.test.abstractclass.generics; + +/** + * @author Andreas Gudian + */ +public interface Identifiable { + T getKey(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Key.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Key.java new file mode 100644 index 0000000000..56e014d199 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Key.java @@ -0,0 +1,14 @@ +/* + * 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.test.abstractclass.generics; + +/** + * @author Andreas Gudian + * + */ +public class Key { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/KeyOfAllBeings.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/KeyOfAllBeings.java new file mode 100644 index 0000000000..ab320ded50 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/KeyOfAllBeings.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.ap.test.abstractclass.generics; + +/** + * @author Andreas Gudian + * + */ +public class KeyOfAllBeings extends Key { + private boolean typeParameterIsResolvedToKeyOfAllBeings; + + public boolean typeParameterIsResolvedToKeyOfAllBeings() { + return typeParameterIsResolvedToKeyOfAllBeings; + } + + public void setTypeParameterIsResolvedToKeyOfAllBeings(boolean typeParameterIsResolvedToKeyOfAllBeings) { + this.typeParameterIsResolvedToKeyOfAllBeings = typeParameterIsResolvedToKeyOfAllBeings; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Target.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/Target.java new file mode 100644 index 0000000000..357e88a081 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/generics/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.ap.test.abstractclass.generics; + +/** + * @author Andreas Gudian + * + */ +public class Target { + private AnimalKey animalKey; + private KeyOfAllBeings keyOfAllBeings; + + public AnimalKey getAnimalKey() { + return animalKey; + } + + public void setAnimalKey(AnimalKey animalKey) { + this.animalKey = animalKey; + } + + public KeyOfAllBeings getKeyOfAllBeings() { + return keyOfAllBeings; + } + + public void setKeyOfAllBeings(KeyOfAllBeings keyOfAllBeings) { + this.keyOfAllBeings = keyOfAllBeings; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java index 5951f9a3df..bab9ca9b71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/AccessibilityTest.java @@ -1,33 +1,18 @@ -/** - * Copyright 2012-2015 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.accessibility; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + import static java.lang.reflect.Modifier.isPrivate; import static java.lang.reflect.Modifier.isProtected; import static java.lang.reflect.Modifier.isPublic; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mapstruct.ap.testutil.IssueKey; -import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Test for different accessibility modifiers @@ -35,10 +20,9 @@ * @author Andreas Gudian */ @WithClasses({ Source.class, Target.class, DefaultSourceTargetMapperAbstr.class, DefaultSourceTargetMapperIfc.class }) -@RunWith( AnnotationProcessorTestRunner.class ) public class AccessibilityTest { - @Test + @ProcessorTest @IssueKey("103") public void testGeneratedModifiersFromAbstractClassAreCorrect() throws Exception { Class defaultFromAbstract = loadForMapper( DefaultSourceTargetMapperAbstr.class ); @@ -50,14 +34,14 @@ public void testGeneratedModifiersFromAbstractClassAreCorrect() throws Exception assertTrue( isDefault( modifiersFor( defaultFromAbstract, "defaultSourceToTarget" ) ) ); } - @Test + @ProcessorTest @IssueKey("103") public void testGeneratedModifiersFromInterfaceAreCorrect() throws Exception { Class defaultFromIfc = loadForMapper( DefaultSourceTargetMapperIfc.class ); assertTrue( isDefault( defaultFromIfc.getModifiers() ) ); - assertTrue( isPublic( modifiersFor( defaultFromIfc, "implicitlyPublicSoureToTarget" ) ) ); + assertTrue( isPublic( modifiersFor( defaultFromIfc, "implicitlyPublicSourceToTarget" ) ) ); } private static Class loadForMapper(Class mapper) throws ClassNotFoundException { diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperAbstr.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperAbstr.java index 8cd9a6af58..d7f7ba46ba 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperAbstr.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperAbstr.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility; diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java index b2182ed21a..027d60b9ec 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility; @@ -25,5 +12,5 @@ */ @Mapper interface DefaultSourceTargetMapperIfc { - Target implicitlyPublicSoureToTarget(Source source); + Target implicitlyPublicSourceToTarget(Source source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/Source.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/Source.java index 43e4543fb6..ab30881998 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility; diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/Target.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/Target.java index 669bb9d66b..253115abb0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility; diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java index 546b741998..2a16c3980f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -27,11 +14,11 @@ * @author Sjaak Derksen */ @Mapper -public abstract class AbstractSourceTargetMapperPrivate extends SourceTargetmapperPrivateBase { +public abstract class AbstractSourceTargetMapperPrivate extends SourceTargetMapperPrivateBase { public static final AbstractSourceTargetMapperPrivate INSTANCE = Mappers.getMapper( AbstractSourceTargetMapperPrivate.class ); - @Mapping(source = "referencedSource", target = "referencedTarget") + @Mapping(target = "referencedTarget", source = "referencedSource") public abstract Target toTarget(Source source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java index 505b2b43ca..ccdba61514 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperProtected.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -27,11 +14,11 @@ * @author Sjaak Derksen */ @Mapper -public abstract class AbstractSourceTargetMapperProtected extends SourceTargetmapperProtectedBase { +public abstract class AbstractSourceTargetMapperProtected extends SourceTargetMapperProtectedBase { public static final AbstractSourceTargetMapperProtected INSTANCE = Mappers.getMapper( AbstractSourceTargetMapperProtected.class ); - @Mapping(source = "referencedSource", target = "referencedTarget") + @Mapping(target = "referencedTarget", source = "referencedSource") public abstract Target toTarget(Source source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java index 48ee688879..dc3d8ea10f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedAccessibilityTest.java @@ -1,32 +1,19 @@ -/** - * Copyright 2012-2015 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.test.accessibility.referenced; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.accessibility.referenced.a.ReferencedMapperDefaultOther; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; /** * Test for different accessibility modifiers @@ -34,69 +21,74 @@ * @author Sjaak Derksen */ @WithClasses( { Source.class, Target.class, ReferencedSource.class, ReferencedTarget.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class ReferencedAccessibilityTest { - @Test - @IssueKey( "206" ) - @WithClasses( { SourceTargetMapperPrivate.class, ReferencedMapperPrivate.class } ) + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @IssueKey("206") + @WithClasses({ SourceTargetMapperPrivate.class, ReferencedMapperPrivate.class }) @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic( type = SourceTargetMapperPrivate.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 35, - messageRegExp = "Can't map property \"org\\.mapstruct\\.ap\\.test\\.accessibility\\." - + "referenced\\.ReferencedSource referencedSource\" to \"org\\.mapstruct\\." - + "ap\\.test\\.accessibility\\.referenced\\.ReferencedTarget referencedTarget\"" ) - } + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SourceTargetMapperPrivate.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22, + message = "Unmapped target property: \"bar\". Mapping from property " + + "\"ReferencedSource referencedSource\" to \"ReferencedTarget referencedTarget\".") + } ) - public void shouldNotBeAbleToAccessPrivateMethodInReferenced() throws Exception { } + public void shouldNotBeAbleToAccessPrivateMethodInReferenced() { + generatedSource.addComparisonToFixtureFor( SourceTargetMapperPrivate.class ); + } - @Test + @ProcessorTest @IssueKey( "206" ) @WithClasses( { SourceTargetMapperDefaultSame.class, ReferencedMapperDefaultSame.class } ) - public void shouldBeAbleToAccessDefaultMethodInReferencedInSamePackage() throws Exception { } + public void shouldBeAbleToAccessDefaultMethodInReferencedInSamePackage() { } - @Test + @ProcessorTest @IssueKey( "206" ) @WithClasses( { SourceTargetMapperProtected.class, ReferencedMapperProtected.class } ) - public void shouldBeAbleToAccessProtectedMethodInReferencedInSamePackage() throws Exception { } + public void shouldBeAbleToAccessProtectedMethodInReferencedInSamePackage() { } - @Test - @IssueKey( "206" ) - @WithClasses( { SourceTargetMapperDefaultOther.class, ReferencedMapperDefaultOther.class } ) + @ProcessorTest + @IssueKey("206") + @WithClasses({ SourceTargetMapperDefaultOther.class, ReferencedMapperDefaultOther.class }) @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic( type = SourceTargetMapperDefaultOther.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 36, - messageRegExp = "Can't map property \"org\\.mapstruct\\.ap\\.test\\.accessibility\\." - + "referenced\\.ReferencedSource referencedSource\" to \"org\\.mapstruct\\." - + "ap\\.test\\.accessibility\\.referenced\\.ReferencedTarget referencedTarget\"" ) - } + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SourceTargetMapperDefaultOther.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 24, + message = "Unmapped target property: \"bar\". Mapping " + + "from property \"ReferencedSource referencedSource\" to \"ReferencedTarget referencedTarget\".") + } ) - public void shouldNotBeAbleToAccessDefaultMethodInReferencedInOtherPackage() throws Exception { } + public void shouldNotBeAbleToAccessDefaultMethodInReferencedInOtherPackage() { + generatedSource.addComparisonToFixtureFor( SourceTargetMapperDefaultOther.class ); + } - @Test + @ProcessorTest @IssueKey( "206" ) - @WithClasses( { AbstractSourceTargetMapperProtected.class, SourceTargetmapperProtectedBase.class } ) - public void shouldBeAbleToAccessProtectedMethodInBase() throws Exception { } + @WithClasses( { AbstractSourceTargetMapperProtected.class, SourceTargetMapperProtectedBase.class } ) + public void shouldBeAbleToAccessProtectedMethodInBase() { } - @Test - @IssueKey( "206" ) - @WithClasses( { AbstractSourceTargetMapperPrivate.class, SourceTargetmapperPrivateBase.class } ) + @ProcessorTest + @IssueKey("206") + @WithClasses({ AbstractSourceTargetMapperPrivate.class, SourceTargetMapperPrivateBase.class }) @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic( type = AbstractSourceTargetMapperPrivate.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 36, - messageRegExp = "Can't map property \"org\\.mapstruct\\.ap\\.test\\.accessibility\\." - + "referenced\\.ReferencedSource referencedSource\" to \"org\\.mapstruct\\." - + "ap\\.test\\.accessibility\\.referenced\\.ReferencedTarget referencedTarget\"" ) - } + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = AbstractSourceTargetMapperPrivate.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 23, + message = "Unmapped target property: \"bar\". Mapping from property " + + "\"ReferencedSource referencedSource\" to \"ReferencedTarget referencedTarget\".") + } ) - public void shouldNotBeAbleToAccessPrivateMethodInBase() throws Exception { } + public void shouldNotBeAbleToAccessPrivateMethodInBase() { + generatedSource.addComparisonToFixtureFor( AbstractSourceTargetMapperPrivate.class ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperDefaultSame.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperDefaultSame.java index 650893fc98..f572eee0a9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperDefaultSame.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperDefaultSame.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -26,7 +13,7 @@ public class ReferencedMapperDefaultSame { ReferencedTarget sourceToTarget( ReferencedSource source ) { ReferencedTarget target = new ReferencedTarget(); - target.setFoo( source.getFoo() ); + target.setBar( source.getFoo() ); return target; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperPrivate.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperPrivate.java index 5662275183..58e1336ca8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperPrivate.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperPrivate.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -26,7 +13,7 @@ public class ReferencedMapperPrivate { @SuppressWarnings("unused") private ReferencedTarget sourceToTarget(ReferencedSource source) { ReferencedTarget target = new ReferencedTarget(); - target.setFoo( source.getFoo() ); + target.setBar( source.getFoo() ); return target; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperProtected.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperProtected.java index 64a6c02488..389486ce4a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperProtected.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedMapperProtected.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -26,7 +13,7 @@ public class ReferencedMapperProtected { protected ReferencedTarget sourceToTarget( ReferencedSource source ) { ReferencedTarget target = new ReferencedTarget(); - target.setFoo( source.getFoo() ); + target.setBar( source.getFoo() ); return target; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedSource.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedSource.java index 5a55bee3ce..e26969551e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedTarget.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedTarget.java index 03601d6abe..1dbfe510d3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/ReferencedTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -22,13 +9,13 @@ * @author Sjaak Derksen */ public class ReferencedTarget { - private String foo; + private String bar; - public String getFoo() { - return foo; + public String getBar() { + return bar; } - public void setFoo(String foo) { - this.foo = foo; + public void setBar(String bar) { + this.bar = bar; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/Source.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/Source.java index 8d2d1671d6..0b833a14ad 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultOther.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultOther.java index 546af2db24..682a258e3d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultOther.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultOther.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -30,8 +17,9 @@ @Mapper(uses = ReferencedMapperDefaultOther.class) public interface SourceTargetMapperDefaultOther { - SourceTargetMapperDefaultOther INSTANCE = Mappers.getMapper( SourceTargetMapperDefaultOther.class ); + SourceTargetMapperDefaultOther INSTANCE = + Mappers.getMapper( SourceTargetMapperDefaultOther.class ); - @Mapping(source = "referencedSource", target = "referencedTarget") + @Mapping(target = "referencedTarget", source = "referencedSource") Target toTarget(Source source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultSame.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultSame.java index 1fafc8729b..536f45d5be 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultSame.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultSame.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -31,6 +18,6 @@ public interface SourceTargetMapperDefaultSame { SourceTargetMapperDefaultSame INSTANCE = Mappers.getMapper( SourceTargetMapperDefaultSame.class ); - @Mapping(source = "referencedSource", target = "referencedTarget") + @Mapping(target = "referencedTarget", source = "referencedSource") Target toTarget(Source source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivate.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivate.java index 4072eab8af..4615ff3e6f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivate.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivate.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -31,6 +18,6 @@ public interface SourceTargetMapperPrivate { SourceTargetMapperPrivate INSTANCE = Mappers.getMapper( SourceTargetMapperPrivate.class ); - @Mapping(source = "referencedSource", target = "referencedTarget") + @Mapping(target = "referencedTarget", source = "referencedSource") Target toTarget(Source source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.java new file mode 100644 index 0000000000..06dbb0cec7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperPrivateBase.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.ap.test.accessibility.referenced; + +/** + * @author Sjaak Derksen + */ +public class SourceTargetMapperPrivateBase { + + @SuppressWarnings("unused") + private ReferencedTarget sourceToTarget(ReferencedSource source) { + ReferencedTarget target = new ReferencedTarget(); + target.setBar( source.getFoo() ); + return target; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtected.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtected.java index 47a7e7b1c9..c18c6127e4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtected.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtected.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; @@ -31,6 +18,6 @@ public interface SourceTargetMapperProtected { SourceTargetMapperProtected INSTANCE = Mappers.getMapper( SourceTargetMapperProtected.class ); - @Mapping(source = "referencedSource", target = "referencedTarget") + @Mapping(target = "referencedTarget", source = "referencedSource") Target toTarget(Source source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.java new file mode 100644 index 0000000000..fe5a50fa37 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperProtectedBase.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.ap.test.accessibility.referenced; + +/** + * + * @author Sjaak Derksen + */ +public class SourceTargetMapperProtectedBase { + + protected ReferencedTarget sourceToTarget( ReferencedSource source ) { + ReferencedTarget target = new ReferencedTarget(); + target.setBar( source.getFoo() ); + return target; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java deleted file mode 100644 index 1227f8ef96..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.accessibility.referenced; - -/** - * @author Sjaak Derksen - */ -public class SourceTargetmapperPrivateBase { - - @SuppressWarnings("unused") - private ReferencedTarget sourceToTarget(ReferencedSource source) { - ReferencedTarget target = new ReferencedTarget(); - target.setFoo( source.getFoo() ); - return target; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java deleted file mode 100644 index 9150f49c78..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.accessibility.referenced; - -/** - * - * @author Sjaak Derksen - */ -public class SourceTargetmapperProtectedBase { - - protected ReferencedTarget sourceToTarget( ReferencedSource source ) { - ReferencedTarget target = new ReferencedTarget(); - target.setFoo( source.getFoo() ); - return target; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/Target.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/Target.java index 3293dca38c..50604a8075 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.accessibility.referenced; diff --git a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/a/ReferencedMapperDefaultOther.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/a/ReferencedMapperDefaultOther.java index cbd7af2d85..7d3ab7014f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/a/ReferencedMapperDefaultOther.java +++ b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/a/ReferencedMapperDefaultOther.java @@ -1,27 +1,13 @@ -/** - * Copyright 2012-2015 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.test.accessibility.referenced.a; import org.mapstruct.ap.test.accessibility.referenced.ReferencedSource; import org.mapstruct.ap.test.accessibility.referenced.ReferencedTarget; - /** * * @author Sjaak Derksen @@ -30,7 +16,7 @@ public class ReferencedMapperDefaultOther { ReferencedTarget sourceToTarget( ReferencedSource source ) { ReferencedTarget target = new ReferencedTarget(); - target.setFoo( source.getFoo() ); + target.setBar( source.getFoo() ); return target; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java new file mode 100644 index 0000000000..ff2c85139f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/AdditionalSupportedOptionsProviderTest.java @@ -0,0 +1,56 @@ +/* + * 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.test.additionalsupportedoptions; + +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.mapstruct.ap.spi.EnumMappingStrategy; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; + +@Execution( ExecutionMode.CONCURRENT ) +public class AdditionalSupportedOptionsProviderTest { + + @ProcessorTest + @WithClasses({ + Pet.class, + PetWithMissing.class, + UnknownEnumMappingStrategyMapper.class + }) + @WithServiceImplementation(CustomAdditionalSupportedOptionsProvider.class) + @WithServiceImplementation(value = UnknownEnumMappingStrategy.class, provides = EnumMappingStrategy.class) + @ProcessorOption(name = "myorg.custom.defaultNullEnumConstant", value = "MISSING") + public void shouldUseConfiguredPrefix() { + assertThat( UnknownEnumMappingStrategyMapper.INSTANCE.map( null ) ) + .isEqualTo( PetWithMissing.MISSING ); + } + + @ProcessorTest(Compiler.JDK) // The eclipse compiler does not parse the error message properly + @WithClasses({ + EmptyMapper.class + }) + @WithServiceImplementation(InvalidAdditionalSupportedOptionsProvider.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + messageRegExp = "Additional SPI options cannot start with \"mapstruct\". Provider " + + "org.mapstruct.ap.test.additionalsupportedoptions.InvalidAdditionalSupportedOptionsProvider@.*" + + " provided option mapstruct.custom.test" + ) + ) + public void shouldFailWhenOptionsProviderUsesMapstructPrefix() { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.java new file mode 100644 index 0000000000..87a994bb4e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/CustomAdditionalSupportedOptionsProvider.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.ap.test.additionalsupportedoptions; + +// tag::documentation[] +import java.util.Collections; +import java.util.Set; + +import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider; + +public class CustomAdditionalSupportedOptionsProvider implements AdditionalSupportedOptionsProvider { + + @Override + public Set getAdditionalSupportedOptions() { + return Collections.singleton( "myorg.custom.defaultNullEnumConstant" ); + } + +} +// end::documentation[] diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.java new file mode 100644 index 0000000000..13a6f0aece --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/EmptyMapper.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.ap.test.additionalsupportedoptions; + +import org.mapstruct.Mapper; + +@Mapper +public interface EmptyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.java new file mode 100644 index 0000000000..1a387abba2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/InvalidAdditionalSupportedOptionsProvider.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.ap.test.additionalsupportedoptions; + +import java.util.Collections; +import java.util.Set; + +import org.mapstruct.ap.spi.AdditionalSupportedOptionsProvider; + +public class InvalidAdditionalSupportedOptionsProvider implements AdditionalSupportedOptionsProvider { + + @Override + public Set getAdditionalSupportedOptions() { + return Collections.singleton( "mapstruct.custom.test" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java new file mode 100644 index 0000000000..8fe9b39558 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/Pet.java @@ -0,0 +1,14 @@ +/* + * 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.test.additionalsupportedoptions; + +public enum Pet { + + DOG, + CAT, + BEAR + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.java new file mode 100644 index 0000000000..21d5f4a649 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/PetWithMissing.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.ap.test.additionalsupportedoptions; + +public enum PetWithMissing { + + DOG, + CAT, + BEAR, + MISSING + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.java new file mode 100644 index 0000000000..ca4403925e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategy.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.ap.test.additionalsupportedoptions; + +// tag::documentation[] +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.spi.DefaultEnumMappingStrategy; +import org.mapstruct.ap.spi.MapStructProcessingEnvironment; + +public class UnknownEnumMappingStrategy extends DefaultEnumMappingStrategy { + + private String defaultNullEnumConstant; + + @Override + public void init(MapStructProcessingEnvironment processingEnvironment) { + super.init( processingEnvironment ); + defaultNullEnumConstant = processingEnvironment.getOptions().get( "myorg.custom.defaultNullEnumConstant" ); + } + + @Override + public String getDefaultNullEnumConstant(TypeElement enumType) { + return defaultNullEnumConstant; + } +} +// end::documentation[] diff --git a/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.java new file mode 100644 index 0000000000..be8c74e227 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/additionalsupportedoptions/UnknownEnumMappingStrategyMapper.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.ap.test.additionalsupportedoptions; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface UnknownEnumMappingStrategyMapper { + + UnknownEnumMappingStrategyMapper INSTANCE = Mappers.getMapper( UnknownEnumMappingStrategyMapper.class ); + + PetWithMissing map(Pet pet); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateBeanMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateBeanMappingMethodMapper.java new file mode 100644 index 0000000000..cc19c2d282 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateBeanMappingMethodMapper.java @@ -0,0 +1,46 @@ +/* + * 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.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateBeanMappingMethodMapper { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + Target map(Source source); + + class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateIterableMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateIterableMappingMethodMapper.java new file mode 100644 index 0000000000..86bef7de45 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateIterableMappingMethodMapper.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.ap.test.annotatewith; + +import java.util.List; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateIterableMappingMethodMapper { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + List toStringList(List integers); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateMapMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateMapMappingMethodMapper.java new file mode 100644 index 0000000000..a972d5e06f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateMapMappingMethodMapper.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.ap.test.annotatewith; + +import java.util.Date; +import java.util.Map; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateMapMappingMethodMapper { + + @MapMapping(valueDateFormat = "dd.MM.yyyy") + @AnnotateWith(CustomMethodOnlyAnnotation.class) + Map longDateMapToStringStringMap(Map source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateStreamMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateStreamMappingMethodMapper.java new file mode 100644 index 0000000000..6321b5590c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateStreamMappingMethodMapper.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.ap.test.annotatewith; + +import java.util.stream.Stream; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateStreamMappingMethodMapper { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + Stream toStringStream(Stream integerStream); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateValueMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateValueMappingMethodMapper.java new file mode 100644 index 0000000000..a9e7c7b519 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateValueMappingMethodMapper.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; + +/** + * @author orange add + */ +@Mapper +public interface AnnotateValueMappingMethodMapper { + + @ValueMappings({ + @ValueMapping(target = "EXISTING", source = "EXISTING"), + @ValueMapping( source = MappingConstants.ANY_REMAINING, target = "OTHER_EXISTING" ) + }) + @AnnotateWith(CustomMethodOnlyAnnotation.class) + AnnotateWithEnum map(String str); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.java new file mode 100644 index 0000000000..b2627fd8ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithEnum.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.ap.test.annotatewith; + +public enum AnnotateWithEnum { + EXISTING, OTHER_EXISTING +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java new file mode 100644 index 0000000000..5dd6a6e8f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotateWithTest.java @@ -0,0 +1,597 @@ +/* + * 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.test.annotatewith; + +import java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ben Zegveld + */ +@IssueKey("1574") +@WithClasses(AnnotateWithEnum.class) +public class AnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ DeprecateAndCustomMapper.class, CustomAnnotation.class }) + public void mapperBecomesDeprecatedAndGetsCustomAnnotation() { + DeprecateAndCustomMapper mapper = Mappers.getMapper( DeprecateAndCustomMapper.class ); + + assertThat( mapper.getClass() ).hasAnnotations( Deprecated.class, CustomAnnotation.class ); + } + + @ProcessorTest + @WithClasses( { + CustomNamedMapper.class, + CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class, + CustomClassOnlyAnnotation.class, + CustomMethodOnlyAnnotation.class, + } ) + public void annotationWithValue() { + generatedSource.addComparisonToFixtureFor( CustomNamedMapper.class ); + } + + @ProcessorTest + @WithClasses( { MultipleArrayValuesMapper.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void annotationWithMultipleValues() { + generatedSource.addComparisonToFixtureFor( MultipleArrayValuesMapper.class ); + } + + @ProcessorTest + @WithClasses( { CustomNamedGenericClassMapper.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void annotationWithCorrectGenericClassValue() { + CustomNamedGenericClassMapper mapper = Mappers.getMapper( CustomNamedGenericClassMapper.class ); + + CustomAnnotationWithParams annotation = mapper.getClass().getAnnotation( CustomAnnotationWithParams.class ); + assertThat( annotation ).isNotNull(); + assertThat( annotation.stringParam() ).isEqualTo( "test" ); + assertThat( annotation.genericTypedClass() ).isEqualTo( Mapper.class ); + } + + @ProcessorTest + @WithClasses( { AnnotationWithoutElementNameMapper.class, CustomAnnotation.class } ) + public void annotateWithoutElementName() { + generatedSource + .forMapper( AnnotationWithoutElementNameMapper.class ) + .content() + .contains( "@CustomAnnotation(value = \"value\")" ); + } + + @ProcessorTest + @WithClasses({ MetaAnnotatedMapper.class, ClassMetaAnnotation.class, CustomClassOnlyAnnotation.class }) + public void metaAnnotationWorks() { + MetaAnnotatedMapper mapper = Mappers.getMapper( MetaAnnotatedMapper.class ); + + assertThat( mapper.getClass() ).hasAnnotation( CustomClassOnlyAnnotation.class ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMissingParameter.class, + line = 15, + message = "Parameter \"required\" is required for annotation \"AnnotationWithRequiredParameter\"." + ) + } + ) + @WithClasses({ ErroneousMapperWithMissingParameter.class, AnnotationWithRequiredParameter.class }) + public void erroneousMapperWithMissingParameter() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMethodOnInterface.class, + line = 15, + message = "Annotation \"CustomMethodOnlyAnnotation\" is not allowed on classes." + ) + } + ) + @WithClasses({ ErroneousMapperWithMethodOnInterface.class, CustomMethodOnlyAnnotation.class }) + public void erroneousMapperWithMethodOnInterface() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMethodOnClass.class, + line = 15, + message = "Annotation \"CustomMethodOnlyAnnotation\" is not allowed on classes." + ) + } + ) + @WithClasses({ ErroneousMapperWithMethodOnClass.class, CustomMethodOnlyAnnotation.class }) + public void erroneousMapperWithMethodOnClass() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithAnnotationOnlyOnInterface.class, + line = 15, + message = "Annotation \"CustomAnnotationOnlyAnnotation\" is not allowed on classes." + ) + } + ) + @WithClasses({ ErroneousMapperWithAnnotationOnlyOnInterface.class, CustomAnnotationOnlyAnnotation.class }) + public void erroneousMapperWithAnnotationOnlyOnInterface() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithAnnotationOnlyOnClass.class, + line = 15, + message = "Annotation \"CustomAnnotationOnlyAnnotation\" is not allowed on classes." + ) + } + ) + @WithClasses({ ErroneousMapperWithAnnotationOnlyOnClass.class, CustomAnnotationOnlyAnnotation.class }) + public void erroneousMapperWithAnnotationOnlyOnClass() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithClassOnMethod.class, + line = 18, + message = "Annotation \"CustomClassOnlyAnnotation\" is not allowed on methods." + ) + } + ) + @WithClasses({ ErroneousMapperWithClassOnMethod.class, CustomClassOnlyAnnotation.class, WithProperties.class }) + public void erroneousMapperWithClassOnMethod() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithUnknownParameter.class, + line = 17, + message = "Unknown parameter \"unknownParameter\" for annotation \"CustomAnnotation\"." + + " Did you mean \"value\"?" + ) + } + ) + @WithClasses({ ErroneousMapperWithUnknownParameter.class, CustomAnnotation.class }) + public void erroneousMapperWithUnknownParameter() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithNonExistantEnum.class, + line = 17, + message = "Enum \"AnnotateWithEnum\" does not have value \"NON_EXISTANT\"." + ) + } + ) + @WithClasses( { ErroneousMapperWithNonExistantEnum.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithNonExistantEnum() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithTooManyParameterValues.class, + line = 17, + message = "Parameter \"stringParam\" has too many value types supplied, type \"String\" is expected" + + " for annotation \"CustomAnnotationWithParams\"." + ) + } + ) + @WithClasses( { ErroneousMapperWithTooManyParameterValues.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithTooManyParameterValues() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 16, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"boolean\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 18, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"byte\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 20, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"char\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 22, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"CustomAnnotationWithParams\"" + + " but of type \"String\" for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 24, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"double\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 26, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"float\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 28, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"int\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 30, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"long\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 32, + alternativeLine = 43, + message = "Parameter \"stringParam\" is not of type \"short\" but of type \"String\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 35, + alternativeLine = 43, + message = "Parameter \"genericTypedClass\" is not of type \"String\" " + + "but of type \"Class\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 36, + alternativeLine = 43, + message = "Parameter \"enumParam\" is not of type \"WrongAnnotateWithEnum\" " + + "but of type \"AnnotateWithEnum\" for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 40, + alternativeLine = 43, + message = "Parameter \"genericTypedClass\" is not of type \"ErroneousMapperWithWrongParameter\" " + + "but of type \"Class\" " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithWrongParameter.class, + line = 42, + alternativeLine = 43, + message = "Parameter \"value\" is not of type \"boolean\" " + + "but of type \"String\" for annotation \"CustomAnnotation\"." + ) + } + ) + @WithClasses({ + ErroneousMapperWithWrongParameter.class, CustomAnnotationWithParams.class, + CustomAnnotationWithParamsContainer.class, WrongAnnotateWithEnum.class, CustomAnnotation.class + }) + public void erroneousMapperWithWrongParameter() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 17, + alternativeLine = 43, + message = "Parameter \"stringParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 18, + alternativeLine = 43, + message = "Parameter \"booleanParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 19, + alternativeLine = 32, + message = "Parameter \"byteParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 20, + alternativeLine = 32, + message = "Parameter \"charParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 21, + alternativeLine = 32, + message = "Parameter \"doubleParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 22, + alternativeLine = 32, + message = "Parameter \"floatParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 23, + alternativeLine = 32, + message = "Parameter \"intParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 24, + alternativeLine = 32, + message = "Parameter \"longParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 25, + alternativeLine = 32, + message = "Parameter \"shortParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 26, + alternativeLine = 32, + message = "Parameter \"genericTypedClass\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMultipleArrayValuesMapper.class, + line = 27, + alternativeLine = 32, + message = "Parameter \"enumParam\" does not accept multiple values " + + "for annotation \"CustomAnnotationWithParams\"." + ) + } + ) + @WithClasses( { ErroneousMultipleArrayValuesMapper.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperUsingMultipleValuesInsteadOfSingle() { + } + + @ProcessorTest + @WithClasses( { MapperWithMissingAnnotationElementName.class, + CustomAnnotationWithTwoAnnotationElements.class } ) + public void mapperWithMissingAnnotationElementNameShouldCompile() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMissingEnumClass.class, + line = 17, + message = "enumClass needs to be defined when using enums." + ) + } + ) + @WithClasses( { ErroneousMapperWithMissingEnumClass.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithMissingEnumClass() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithMissingEnums.class, + line = 17, + message = "enums needs to be defined when using enumClass." + ) + } + ) + @WithClasses( { ErroneousMapperWithMissingEnums.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithMissingEnums() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithRepeatOfNotRepeatableAnnotation.class, + line = 16, + alternativeLine = 17, + message = "Annotation \"CustomAnnotation\" is not repeatable." + ) + } + ) + @WithClasses( { ErroneousMapperWithRepeatOfNotRepeatableAnnotation.class, CustomAnnotation.class } ) + public void erroneousMapperWithRepeatOfNotRepeatableAnnotation() { + } + + @ProcessorTest + @WithClasses( { MapperWithRepeatableAnnotation.class, CustomRepeatableAnnotation.class, + CustomRepeatableAnnotationContainer.class } ) + public void mapperWithRepeatableAnnotationShouldCompile() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapperWithParameterRepeat.class, + line = 18, + message = "Parameter \"stringParam\" must not be defined more than once." + ) + } + ) + @WithClasses( { ErroneousMapperWithParameterRepeat.class, CustomAnnotationWithParamsContainer.class, + CustomAnnotationWithParams.class } ) + public void erroneousMapperWithParameterRepeat() { + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = MapperWithIdenticalAnnotationRepeated.class, + line = 16, + alternativeLine = 17, + message = "Annotation \"CustomRepeatableAnnotation\" is already present " + + "with the same elements configuration." + ) + } + ) + @WithClasses( { MapperWithIdenticalAnnotationRepeated.class, CustomRepeatableAnnotation.class, + CustomRepeatableAnnotationContainer.class } ) + public void mapperWithIdenticalAnnotationRepeated() { + } + + @ProcessorTest + @WithClasses( {AnnotateBeanMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} ) + public void beanMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateBeanMappingMethodMapper mapper = Mappers.getMapper( AnnotateBeanMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "map", AnnotateBeanMappingMethodMapper.Source.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + + @ProcessorTest + @WithClasses( {AnnotateIterableMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} ) + public void iterableMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateIterableMappingMethodMapper mapper = Mappers.getMapper( AnnotateIterableMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "toStringList", List.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + + @ProcessorTest + @WithClasses( {AnnotateMapMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} ) + public void mapMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateMapMappingMethodMapper mapper = Mappers.getMapper( AnnotateMapMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "longDateMapToStringStringMap", Map.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + + @ProcessorTest + @WithClasses( {AnnotateStreamMappingMethodMapper.class, CustomMethodOnlyAnnotation.class} ) + public void streamMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateStreamMappingMethodMapper mapper = Mappers.getMapper( AnnotateStreamMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "toStringStream", Stream.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } + + @ProcessorTest + @WithClasses( {AnnotateValueMappingMethodMapper.class, AnnotateWithEnum.class, CustomMethodOnlyAnnotation.class} ) + public void valueMappingMethodWithCorrectCustomAnnotation() throws NoSuchMethodException { + AnnotateValueMappingMethodMapper mapper = Mappers.getMapper( AnnotateValueMappingMethodMapper.class ); + Method method = mapper.getClass().getMethod( "map", String.class ); + assertThat( method.getAnnotation( CustomMethodOnlyAnnotation.class ) ).isNotNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.java new file mode 100644 index 0000000000..183b865928 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithRequiredParameter.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.ap.test.annotatewith; + +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.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface AnnotationWithRequiredParameter { + String required(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.java new file mode 100644 index 0000000000..8bb83ba562 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/AnnotationWithoutElementNameMapper.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +@Mapper +@AnnotateWith( value = CustomAnnotation.class, elements = @AnnotateWith.Element( strings = "value" ) ) +public interface AnnotationWithoutElementNameMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.java new file mode 100644 index 0000000000..057cb384fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ClassMetaAnnotation.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.ap.test.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.AnnotateWith; + +@Retention( RetentionPolicy.RUNTIME ) +@Target( { ElementType.TYPE } ) +@AnnotateWith( CustomClassOnlyAnnotation.class ) +public @interface ClassMetaAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.java new file mode 100644 index 0000000000..71ee6f12e3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotation.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.ap.test.annotatewith; + +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.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface CustomAnnotation { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.java new file mode 100644 index 0000000000..52d3e386a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationOnlyAnnotation.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.ap.test.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention(RUNTIME) +@Target({ ANNOTATION_TYPE }) +public @interface CustomAnnotationOnlyAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java new file mode 100644 index 0000000000..bc25302fc1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParams.java @@ -0,0 +1,64 @@ +/* + * 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.test.annotatewith; + +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.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { METHOD, TYPE } ) +@Repeatable( CustomAnnotationWithParamsContainer.class ) +public @interface CustomAnnotationWithParams { + String stringParam(); + + Class genericTypedClass() default CustomAnnotationWithParams.class; + + AnnotateWithEnum enumParam() default AnnotateWithEnum.EXISTING; + + byte byteParam() default 0x00; + + char charParam() default 'a'; + + double doubleParam() default 0.0; + + float floatParam() default 0.0f; + + int intParam() default 0; + + long longParam() default 0L; + + short shortParam() default 0; + + boolean booleanParam() default false; + + short[] shortArray() default {}; + + byte[] byteArray() default {}; + + int[] intArray() default {}; + + long[] longArray() default {}; + + float[] floatArray() default {}; + + double[] doubleArray() default {}; + + char[] charArray() default {}; + + boolean[] booleanArray() default {}; + + String[] stringArray() default {}; + + Class[] classArray() default {}; + + AnnotateWithEnum[] enumArray() default {}; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.java new file mode 100644 index 0000000000..51ed62885c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithParamsContainer.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.ap.test.annotatewith; + +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.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface CustomAnnotationWithParamsContainer { + CustomAnnotationWithParams[] value() default {}; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.java new file mode 100644 index 0000000000..fb8c5c69f8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomAnnotationWithTwoAnnotationElements.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.ap.test.annotatewith; + +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.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface CustomAnnotationWithTwoAnnotationElements { + String value() default ""; + boolean namedAnnotationElement() default false; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.java new file mode 100644 index 0000000000..858c8fb52f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomClassOnlyAnnotation.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.ap.test.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE } ) +public @interface CustomClassOnlyAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.java new file mode 100644 index 0000000000..1dfe8e734e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomMethodOnlyAnnotation.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.ap.test.annotatewith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Retention( RUNTIME ) +@Target( { METHOD } ) +public @interface CustomMethodOnlyAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.java new file mode 100644 index 0000000000..a320d56bba --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedGenericClassMapper.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringParam", strings = "test" ), + @Element( name = "genericTypedClass", classes = Mapper.class ) +} ) +public interface CustomNamedGenericClassMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java new file mode 100644 index 0000000000..9f48d8c764 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomNamedMapper.java @@ -0,0 +1,78 @@ +/* + * 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.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( CustomClassOnlyAnnotation.class ) +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringArray", strings = "test" ), + @Element( name = "stringParam", strings = "test" ), + @Element( name = "booleanArray", booleans = true ), + @Element( name = "booleanParam", booleans = true ), + @Element( name = "byteArray", bytes = 0x10 ), + @Element( name = "byteParam", bytes = 0x13 ), + @Element( name = "charArray", chars = 'd' ), + @Element( name = "charParam", chars = 'a' ), + @Element( name = "enumArray", enumClass = AnnotateWithEnum.class, enums = "EXISTING" ), + @Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = "EXISTING" ), + @Element( name = "doubleArray", doubles = 0.3 ), + @Element( name = "doubleParam", doubles = 1.2 ), + @Element( name = "floatArray", floats = 0.3f ), + @Element( name = "floatParam", floats = 1.2f ), + @Element( name = "intArray", ints = 3 ), + @Element( name = "intParam", ints = 1 ), + @Element( name = "longArray", longs = 3L ), + @Element( name = "longParam", longs = 1L ), + @Element( name = "shortArray", shorts = 3 ), + @Element( name = "shortParam", shorts = 1 ) +} ) +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element(name = "stringParam", strings = "single value") +}) +public interface CustomNamedMapper { + + @AnnotateWith(value = CustomAnnotationWithParams.class, elements = { + @Element(name = "stringParam", strings = "double method value"), + @Element(name = "stringArray", strings = { "first", "second" }), + }) + @AnnotateWith(value = CustomAnnotationWithParams.class, elements = { + @Element(name = "stringParam", strings = "single method value") + }) + @AnnotateWith( CustomMethodOnlyAnnotation.class ) + Target map(Source source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.java new file mode 100644 index 0000000000..d7c0c85107 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotation.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.ap.test.annotatewith; + +import java.lang.annotation.Repeatable; +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.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +@Repeatable( CustomRepeatableAnnotationContainer.class ) +public @interface CustomRepeatableAnnotation { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.java new file mode 100644 index 0000000000..8de3a8bf0a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/CustomRepeatableAnnotationContainer.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.ap.test.annotatewith; + +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.RUNTIME; + +@Retention( RUNTIME ) +@Target( { TYPE, METHOD } ) +public @interface CustomRepeatableAnnotationContainer { + CustomRepeatableAnnotation[] value() default {}; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.java new file mode 100644 index 0000000000..809ad5702a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/DeprecateAndCustomMapper.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( Deprecated.class ) +@AnnotateWith( CustomAnnotation.class ) +public interface DeprecateAndCustomMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.java new file mode 100644 index 0000000000..d617e8d0fc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnClass.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +@AnnotateWith( value = CustomAnnotationOnlyAnnotation.class ) +public abstract class ErroneousMapperWithAnnotationOnlyOnClass { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.java new file mode 100644 index 0000000000..4e914b30a3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithAnnotationOnlyOnInterface.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +@AnnotateWith( value = CustomAnnotationOnlyAnnotation.class ) +public interface ErroneousMapperWithAnnotationOnlyOnInterface { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.java new file mode 100644 index 0000000000..c34bce304f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithClassOnMethod.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.WithProperties; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface ErroneousMapperWithClassOnMethod { + + @AnnotateWith( value = CustomClassOnlyAnnotation.class ) + WithProperties toString(String string); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.java new file mode 100644 index 0000000000..262880d6f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnClass.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomMethodOnlyAnnotation.class ) +public abstract class ErroneousMapperWithMethodOnClass { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.java new file mode 100644 index 0000000000..75f9039093 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMethodOnInterface.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomMethodOnlyAnnotation.class ) +public interface ErroneousMapperWithMethodOnInterface { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.java new file mode 100644 index 0000000000..6905a363f2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnumClass.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = { @Element( name = "enumParam", enums = "EXISTING" ), + @Element( name = "stringParam", strings = "required" ) } +) +public interface ErroneousMapperWithMissingEnumClass { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.java new file mode 100644 index 0000000000..fc7096d290 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingEnums.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @Element( name = "stringParam", strings = "required", enumClass = AnnotateWithEnum.class ) +) +public interface ErroneousMapperWithMissingEnums { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.java new file mode 100644 index 0000000000..40ffa53094 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithMissingParameter.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( AnnotationWithRequiredParameter.class ) +public interface ErroneousMapperWithMissingParameter { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.java new file mode 100644 index 0000000000..1ed85b990d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithNonExistantEnum.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = { @Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = "NON_EXISTANT" ), + @Element( name = "stringParam", strings = "required" ) } +) +public interface ErroneousMapperWithNonExistantEnum { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.java new file mode 100644 index 0000000000..d75c7a4ee4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithParameterRepeat.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringParam", strings = "test" ), + @Element( name = "stringParam", strings = "otherValue" ) +} ) +public interface ErroneousMapperWithParameterRepeat { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.java new file mode 100644 index 0000000000..be9596ea46 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithRepeatOfNotRepeatableAnnotation.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotation.class ) +@AnnotateWith( value = CustomAnnotation.class ) +public interface ErroneousMapperWithRepeatOfNotRepeatableAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.java new file mode 100644 index 0000000000..4b6c20c39b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithTooManyParameterValues.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @Element( name = "stringParam", booleans = true, strings = "test" ) +) +public interface ErroneousMapperWithTooManyParameterValues { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.java new file mode 100644 index 0000000000..c705dc88f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithUnknownParameter.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotation.class, + elements = @Element( name = "unknownParameter", strings = "unknown" ) +) +public interface ErroneousMapperWithUnknownParameter { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.java new file mode 100644 index 0000000000..3251122b22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMapperWithWrongParameter.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", booleans = true ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", bytes = 0x12 ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", chars = 'a' ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", classes = CustomAnnotationWithParams.class ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", doubles = 12.34 ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", floats = 12.34f ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", ints = 1234 ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", longs = 1234L ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, + elements = @AnnotateWith.Element( name = "stringParam", shorts = 12 ) ) +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @AnnotateWith.Element( name = "stringParam", strings = "correctValue" ), + @AnnotateWith.Element( name = "genericTypedClass", strings = "wrong" ), + @AnnotateWith.Element( name = "enumParam", enumClass = WrongAnnotateWithEnum.class, enums = "EXISTING" ) +} ) +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @AnnotateWith.Element( name = "stringParam", strings = "correctValue" ), + @AnnotateWith.Element( name = "genericTypedClass", classes = ErroneousMapperWithWrongParameter.class ) +} ) +@AnnotateWith( value = CustomAnnotation.class, elements = @AnnotateWith.Element( booleans = true ) ) +public interface ErroneousMapperWithWrongParameter { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.java new file mode 100644 index 0000000000..f6bf7629cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/ErroneousMultipleArrayValuesMapper.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringParam", strings = { "test1", "test2" } ), + @Element( name = "booleanParam", booleans = { false, true } ), + @Element( name = "byteParam", bytes = { 0x08, 0x1f } ), + @Element( name = "charParam", chars = { 'b', 'c' } ), + @Element( name = "doubleParam", doubles = { 1.2, 3.4 } ), + @Element( name = "floatParam", floats = { 1.2f, 3.4f } ), + @Element( name = "intParam", ints = { 12, 34 } ), + @Element( name = "longParam", longs = { 12L, 34L } ), + @Element( name = "shortParam", shorts = { 12, 34 } ), + @Element( name = "genericTypedClass", classes = { Mapper.class, CustomAnnotationWithParams.class } ), + @Element( name = "enumParam", enumClass = AnnotateWithEnum.class, enums = { "EXISTING", "OTHER_EXISTING" } ) +} ) +public interface ErroneousMultipleArrayValuesMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.java new file mode 100644 index 0000000000..0959ee305a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithIdenticalAnnotationRepeated.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "identical" ) ) +@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "identical" ) ) +public interface MapperWithIdenticalAnnotationRepeated { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.java new file mode 100644 index 0000000000..ed9523c545 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithMissingAnnotationElementName.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithTwoAnnotationElements.class, elements = { + @AnnotateWith.Element( strings = "unnamed annotation element" ), + @AnnotateWith.Element( name = "namedAnnotationElement", booleans = false ) +} ) +public abstract class MapperWithMissingAnnotationElementName { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.java new file mode 100644 index 0000000000..b66ec68261 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MapperWithRepeatableAnnotation.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomRepeatableAnnotation.class ) +@AnnotateWith( value = CustomRepeatableAnnotation.class, elements = @AnnotateWith.Element( strings = "different" ) ) +public interface MapperWithRepeatableAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java new file mode 100644 index 0000000000..2dddb17f83 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MetaAnnotatedMapper.java @@ -0,0 +1,14 @@ +/* + * 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.test.annotatewith; + +import org.mapstruct.Mapper; + +@ClassMetaAnnotation +@Mapper +public interface MetaAnnotatedMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.java new file mode 100644 index 0000000000..a93d54c283 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MethodMetaAnnotation.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.ap.test.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.AnnotateWith; + +@Retention( RetentionPolicy.RUNTIME ) +@Target( { ElementType.METHOD } ) +@AnnotateWith( CustomMethodOnlyAnnotation.class ) +public @interface MethodMetaAnnotation { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.java new file mode 100644 index 0000000000..2ccdbb0ea2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/MultipleArrayValuesMapper.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.ap.test.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.AnnotateWith.Element; +import org.mapstruct.Mapper; + +/** + * @author Ben Zegveld + */ +@Mapper +@AnnotateWith( value = CustomAnnotationWithParams.class, elements = { + @Element( name = "stringArray", strings = { "test1", "test2" } ), + @Element( name = "booleanArray", booleans = { false, true } ), + @Element( name = "byteArray", bytes = { 0x08, 0x1f } ), + @Element( name = "charArray", chars = { 'b', 'c' } ), + @Element( name = "doubleArray", doubles = { 1.2, 3.4 } ), + @Element( name = "floatArray", floats = { 1.2f, 3.4f } ), + @Element( name = "intArray", ints = { 12, 34 } ), + @Element( name = "longArray", longs = { 12L, 34L } ), + @Element( name = "shortArray", shorts = { 12, 34 } ), + @Element( name = "classArray", classes = { Mapper.class, CustomAnnotationWithParams.class } ), + @Element( name = "stringParam", strings = "required parameter" ) +} ) +public interface MultipleArrayValuesMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.java new file mode 100644 index 0000000000..677a6cc6d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/WrongAnnotateWithEnum.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.ap.test.annotatewith; + +public enum WrongAnnotateWithEnum { + EXISTING +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.java new file mode 100644 index 0000000000..d0164e8be0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithClass.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.ap.test.annotatewith.deprecated; + +import org.mapstruct.Mapper; + +@Mapper +@Deprecated +public class DeprecatedMapperWithClass { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.java new file mode 100644 index 0000000000..b6c2c8eb62 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedMapperWithMethod.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.ap.test.annotatewith.deprecated; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; + +@Mapper +public interface DeprecatedMapperWithMethod { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + @Deprecated + Target map(Source source); + + class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java new file mode 100644 index 0000000000..42bb5d9ce5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.java @@ -0,0 +1,57 @@ +/* + * 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.test.annotatewith.deprecated; + +import java.lang.reflect.Method; + +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author orange add + */ +public class DeprecatedTest { + + @ProcessorTest + @WithClasses( { DeprecatedMapperWithMethod.class, CustomMethodOnlyAnnotation.class} ) + public void deprecatedWithMethodCorrectCopy() throws NoSuchMethodException { + DeprecatedMapperWithMethod mapper = Mappers.getMapper( DeprecatedMapperWithMethod.class ); + Method method = mapper.getClass().getMethod( "map", DeprecatedMapperWithMethod.Source.class ); + Deprecated annotation = method.getAnnotation( Deprecated.class ); + assertThat( annotation ).isNotNull(); + } + + @ProcessorTest + @WithClasses(DeprecatedMapperWithClass.class) + public void deprecatedWithClassCorrectCopy() { + DeprecatedMapperWithClass mapper = Mappers.getMapper( DeprecatedMapperWithClass.class ); + Deprecated annotation = mapper.getClass().getAnnotation( Deprecated.class ); + assertThat( annotation ).isNotNull(); + } + + @ProcessorTest + @WithClasses(RepeatDeprecatedMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = RepeatDeprecatedMapper.class, + message = "Annotation \"Deprecated\" is already present with the " + + "same elements configuration." + ) + } + ) + public void deprecatedWithRepeat() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.java new file mode 100644 index 0000000000..dd085f101c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/RepeatDeprecatedMapper.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.ap.test.annotatewith.deprecated; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +@Mapper +@Deprecated +@AnnotateWith(Deprecated.class) +public class RepeatDeprecatedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.java new file mode 100644 index 0000000000..2e0507f5ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithClass.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.ap.test.annotatewith.deprecated.jdk11; + +import org.mapstruct.Mapper; + +@Mapper +@Deprecated(since = "11") +public class DeprecatedMapperWithClass { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.java new file mode 100644 index 0000000000..bf898e2043 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedMapperWithMethod.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.ap.test.annotatewith.deprecated.jdk11; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; + +@Mapper +public interface DeprecatedMapperWithMethod { + + @AnnotateWith(CustomMethodOnlyAnnotation.class) + @Deprecated(since = "18", forRemoval = false) + Target map(Source source); + + class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java new file mode 100644 index 0000000000..dc496a22fc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.java @@ -0,0 +1,64 @@ +/* + * 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.test.annotatewith.deprecated.jdk11; + +import java.lang.reflect.Method; + +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author orange add + */ +public class DeprecatedTest { + @ProcessorTest + @WithClasses({ DeprecatedMapperWithMethod.class, CustomMethodOnlyAnnotation.class}) + public void deprecatedWithMethodCorrectCopyForJdk11() throws NoSuchMethodException { + DeprecatedMapperWithMethod mapper = Mappers.getMapper( DeprecatedMapperWithMethod.class ); + Method method = mapper.getClass().getMethod( "map", DeprecatedMapperWithMethod.Source.class ); + Deprecated annotation = method.getAnnotation( Deprecated.class ); + assertThat( annotation ).isNotNull(); + assertThat( annotation.since() ).isEqualTo( "18" ); + assertThat( annotation.forRemoval() ).isEqualTo( false ); + } + + @ProcessorTest + @WithClasses(DeprecatedMapperWithClass.class) + public void deprecatedWithClassCorrectCopyForJdk11() { + DeprecatedMapperWithClass mapper = Mappers.getMapper( DeprecatedMapperWithClass.class ); + Deprecated annotation = mapper.getClass().getAnnotation( Deprecated.class ); + assertThat( annotation ).isNotNull(); + assertThat( annotation.since() ).isEqualTo( "11" ); + } + + @ProcessorTest + @WithClasses( { RepeatDeprecatedMapperWithParams.class}) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = RepeatDeprecatedMapperWithParams.class, + message = "Annotation \"Deprecated\" is already present with the " + + "same elements configuration." + ) + } + ) + public void bothExistPriorityAnnotateWithForJdk11() { + RepeatDeprecatedMapperWithParams mapper = Mappers.getMapper( RepeatDeprecatedMapperWithParams.class ); + Deprecated deprecated = mapper.getClass().getAnnotation( Deprecated.class ); + assertThat( deprecated ).isNotNull(); + assertThat( deprecated.since() ).isEqualTo( "1.5" ); + assertThat( deprecated.forRemoval() ).isEqualTo( false ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.java new file mode 100644 index 0000000000..971791bfd2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/RepeatDeprecatedMapperWithParams.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.ap.test.annotatewith.deprecated.jdk11; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +@Mapper +@Deprecated( since = "1.8" ) +@AnnotateWith( + value = Deprecated.class, + elements = { + @AnnotateWith.Element( name = "forRemoval", booleans = false), + @AnnotateWith.Element( name = "since", strings = "1.5") + } +) +public class RepeatDeprecatedMapperWithParams { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java index 392b3a53d6..fe33893417 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/array/ArrayMappingTest.java @@ -1,95 +1,195 @@ -/** - * Copyright 2012-2015 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.array; import java.util.Arrays; import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.array._target.GenericScientistDto; import org.mapstruct.ap.test.array._target.ScientistDto; +import org.mapstruct.ap.test.array.source.GenericScientist; import org.mapstruct.ap.test.array.source.Scientist; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; -@WithClasses( { Scientist.class, ScientistDto.class, ScienceMapper.class } ) -@RunWith(AnnotationProcessorTestRunner.class) +@WithClasses( { Scientist.class, ScientistDto.class, GenericScientist.class, GenericScientistDto.class, + ScienceMapper.class } ) @IssueKey("108") public class ArrayMappingTest { - @Test + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( ScienceMapper.class ); + + @ProcessorTest public void shouldCopyArraysInBean() { Scientist source = new Scientist("Bob"); source.setPublications( new String[]{ "the Lancet", "Nature" } ); + source.publicPublications = new String[] { "public the Lancet", "public Nature" }; ScientistDto dto = ScienceMapper.INSTANCE.scientistToDto( source ); assertThat( dto ).isNotNull(); assertThat( dto ).isNotEqualTo( source ); - assertThat( dto.getPublications() ).containsOnly( "the Lancet", "Nature"); + assertThat( dto.getPublications() ).containsOnly( "the Lancet", "Nature" ); + assertThat( dto.publicPublications ).containsOnly( "public the Lancet", "public Nature" ); } - @Test + @ProcessorTest public void shouldForgeMappingForIntToString() { Scientist source = new Scientist("Bob"); source.setPublicationYears( new String[]{"1993", "1997"} ); + source.publicPublicationYears = new String[]{"1994", "1998"}; ScientistDto dto = ScienceMapper.INSTANCE.scientistToDto( source ); assertThat( dto ).isNotNull(); assertThat( dto.getPublicationYears() ).containsOnly( 1993, 1997 ); + assertThat( dto.publicPublicationYears ).containsOnly( 1994, 1998 ); } + @ProcessorTest + public void shouldMapArrayToArrayAndNullToNull() { + ScientistDto[] dtos = ScienceMapper.INSTANCE + .scientistsToDtosReturnNull( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.scientistsToDtosReturnNull( (Scientist[]) null ) ).isNull(); + } - @Test - public void shouldMapArrayToArray() { + @ProcessorTest + public void shouldMapArrayToArrayAndNullToDefault() { ScientistDto[] dtos = ScienceMapper.INSTANCE - .scientistsToDtos( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); + .scientistsToDtosReturnDefault( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); assertThat( dtos ).isNotNull(); - assertThat( dtos ).onProperty( "name" ).containsOnly( "Bob", "Larry" ); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.scientistsToDtosReturnDefault( (Scientist[]) null ) ).isEmpty(); } - @Test - public void shouldMapListToArray() { + @ProcessorTest + public void shouldMapListToArrayAndNullToNull() { ScientistDto[] dtos = ScienceMapper.INSTANCE - .scientistsToDtos( Arrays.asList( new Scientist( "Bob" ), new Scientist( "Larry" ) ) ); + .scientistsToDtosReturnNull( Arrays.asList( new Scientist( "Bob" ), new Scientist( "Larry" ) ) ); assertThat( dtos ).isNotNull(); - assertThat( dtos ).onProperty( "name" ).containsOnly( "Bob", "Larry" ); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.scientistsToDtosReturnNull( (List) null ) ).isNull(); } - @Test - public void shouldMapArrayToList() { + @ProcessorTest + public void shouldMapListToArrayAndNullToDefault() { + ScientistDto[] dtos = ScienceMapper.INSTANCE + .scientistsToDtosReturnDefault( Arrays.asList( new Scientist( "Bob" ), new Scientist( "Larry" ) ) ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.scientistsToDtosReturnDefault( (List) null ) ).isEmpty(); + } + + @ProcessorTest + public void shouldMapArrayToListAndNullToNull() { + List dtos = ScienceMapper.INSTANCE + .scientistsToDtosAsListReturnNull( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.scientistsToDtosAsListReturnNull( null ) ).isNull(); + } + + @ProcessorTest + public void shouldMapArrayToListAndNullToDefault() { List dtos = ScienceMapper.INSTANCE - .scientistsToDtosAsList( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); + .scientistsToDtosAsListReturnDefault( + new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.scientistsToDtosAsListReturnDefault( null ) ).isEmpty(); + } + + @ProcessorTest + @SuppressWarnings("unchecked") + public void shouldMapGenericArrayToGenericArrayAndNullToNull() { + GenericScientistDto[] dtos = ScienceMapper.INSTANCE + .genericScientistToDtosReturnNull( + new GenericScientist[]{ new GenericScientist<>( "Bob" ), new GenericScientist<>( "Larry" ) } ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.genericScientistToDtosReturnNull( (GenericScientist[]) null ) ) + .isNull(); + } + + @ProcessorTest + @SuppressWarnings("unchecked") + public void shouldMapGenericArrayToGenericArrayAndNullToDefault() { + GenericScientistDto[] dtos = ScienceMapper.INSTANCE.genericScientistToDtosReturnDefault( + new GenericScientist[]{ new GenericScientist<>( "Bob" ), new GenericScientist<>( "Larry" ) } ); assertThat( dtos ).isNotNull(); - assertThat( dtos ).onProperty( "name" ).containsOnly( "Bob", "Larry" ); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.genericScientistToDtosReturnDefault( (GenericScientist[]) null ) ) + .isEmpty(); } - @Test + @ProcessorTest + public void shouldMapListToGenericArrayAndNullToNull() { + GenericScientistDto[] dtos = ScienceMapper.INSTANCE.genericScientistToDtosReturnNull( + Arrays.asList( new GenericScientist<>( "Bob" ), new GenericScientist<>( "Larry" ) ) ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.genericScientistToDtosReturnNull( (List>) null ) ) + .isNull(); + } + + @ProcessorTest + public void shouldMapListToGenericArrayAndNullToDefault() { + GenericScientistDto[] dtos = ScienceMapper.INSTANCE + .genericScientistToDtosReturnDefault( + Arrays.asList( new GenericScientist<>("Bob"), new GenericScientist<>( "Larry" ) ) ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE + .genericScientistToDtosReturnDefault( (List>) null ) ).isEmpty(); + } + + @ProcessorTest + @SuppressWarnings("unchecked") + public void shouldMapGenericArrayToListAndNullToNull() { + List> dtos = ScienceMapper.INSTANCE.genericScientistToDtosAsList( + new GenericScientist[]{ new GenericScientist<>( "Bob" ), new GenericScientist<>( "Larry" ) } ); + + assertThat( dtos ).isNotNull(); + assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); + + assertThat( ScienceMapper.INSTANCE.genericScientistToDtosAsList( null ) ).isNull(); + } + + @ProcessorTest public void shouldMapArrayToArrayExistingSmallerSizedTarget() { ScientistDto[] existingTarget = new ScientistDto[]{ new ScientistDto( "Jim" ) }; @@ -99,10 +199,10 @@ public void shouldMapArrayToArrayExistingSmallerSizedTarget() { assertThat( target ).isNotNull(); assertThat( target ).isEqualTo( existingTarget ); - assertThat( target ).onProperty( "name" ).containsOnly( "Bob" ); + assertThat( target ).extracting( "name" ).containsOnly( "Bob" ); } - @Test + @ProcessorTest public void shouldMapArrayToArrayExistingEqualSizedTarget() { ScientistDto[] existingTarget = new ScientistDto[]{ new ScientistDto( "Jim" ), new ScientistDto( "Bart" ) }; @@ -112,10 +212,10 @@ public void shouldMapArrayToArrayExistingEqualSizedTarget() { assertThat( target ).isNotNull(); assertThat( target ).isEqualTo( existingTarget ); - assertThat( target ).onProperty( "name" ).containsOnly( "Bob", "Larry" ); + assertThat( target ).extracting( "name" ).containsOnly( "Bob", "Larry" ); } - @Test + @ProcessorTest public void shouldMapArrayToArrayExistingLargerSizedTarget() { ScientistDto[] existingTarget = @@ -126,101 +226,113 @@ public void shouldMapArrayToArrayExistingLargerSizedTarget() { assertThat( target ).isNotNull(); assertThat( target ).isEqualTo( existingTarget ); - assertThat( target ).onProperty( "name" ).containsOnly( "Bob", "Larry", "John" ); + assertThat( target ).extracting( "name" ).containsOnly( "Bob", "Larry", "John" ); } - @Test - public void shouldMapTargetToNullWhenNullSource() { - // TODO: What about existing target? + @ProcessorTest + public void shouldReturnMapTargetWhenNullSource() { ScientistDto[] existingTarget = new ScientistDto[]{ new ScientistDto( "Jim" ) }; ScientistDto[] target = ScienceMapper.INSTANCE.scientistsToDtos( null, existingTarget ); - assertThat( target ).isNull(); - assertThat( existingTarget ).onProperty( "name" ).containsOnly( "Jim" ); + assertThat( target ).isNotNull(); + assertThat( target ).isEqualTo( existingTarget ); + assertThat( existingTarget ).extracting( "name" ).containsOnly( "Jim" ); } @IssueKey("534") - @Test - public void shouldMapbooleanWhenReturnDefault() { + @ProcessorTest + public void shouldMapBooleanWhenReturnDefault() { boolean[] existingTarget = new boolean[]{true}; boolean[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( false ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( false ); assertThat( ScienceMapper.INSTANCE.nvmMapping( null ) ).isEmpty(); } - @Test - public void shouldMapshortWhenReturnDefault() { + @ProcessorTest + public void shouldMapShortWhenReturnDefault() { short[] existingTarget = new short[]{ 5 }; short[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( new short[] { 0 } ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( new short[] { 0 } ); } - @Test - public void shouldMapcharWhenReturnDefault() { + @ProcessorTest + public void shouldMapCharWhenReturnDefault() { char[] existingTarget = new char[]{ 'a' }; char[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); assertThat( target ).containsOnly( new char[] { 0 } ); + assertThat( target ).isEqualTo( existingTarget ); assertThat( existingTarget ).containsOnly( new char[] { 0 } ); } - @Test - public void shouldMapintWhenReturnDefault() { + @ProcessorTest + public void shouldMapIntWhenReturnDefault() { int[] existingTarget = new int[]{ 5 }; int[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); - assertThat( target ).containsOnly( new int[] { 0 } ); - assertThat( existingTarget ).containsOnly( new int[] { 0 } ); + assertThat( target ).containsOnly( 0 ); + assertThat( target ).isEqualTo( existingTarget ); + assertThat( existingTarget ).containsOnly( 0 ); } - @Test - public void shouldMaplongWhenReturnDefault() { + @ProcessorTest + public void shouldMapLongWhenReturnDefault() { long[] existingTarget = new long[]{ 5L }; long[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); - assertThat( target ).containsOnly( new long[] { 0L } ); - assertThat( existingTarget ).containsOnly( new long[] { 0L } ); + assertThat( target ).containsOnly( 0L ); + assertThat( target ).isEqualTo( existingTarget ); + assertThat( existingTarget ).containsOnly( 0L ); } - @Test - public void shouldMapfloatWhenReturnDefault() { + @ProcessorTest + public void shouldMapFloatWhenReturnDefault() { float[] existingTarget = new float[]{ 3.1f }; float[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); - assertThat( target ).containsOnly( new float[] { 0.0f } ); - assertThat( existingTarget ).containsOnly( new float[] { 0.0f } ); + assertThat( target ).containsOnly( 0.0f ); + assertThat( target ).isEqualTo( existingTarget ); + assertThat( existingTarget ).containsOnly( 0.0f ); } - @Test - public void shouldMapdoubleWhenReturnDefault() { + @ProcessorTest + public void shouldMapDoubleWhenReturnDefault() { double[] existingTarget = new double[]{ 5.0d }; double[] target = ScienceMapper.INSTANCE.nvmMapping( null, existingTarget ); - assertThat( target ).containsOnly( new double[] { 0.0d } ); - assertThat( existingTarget ).containsOnly( new double[] { 0.0d } ); + assertThat( target ).containsOnly( 0.0d ); + assertThat( target ).isEqualTo( existingTarget ); + assertThat( existingTarget ).containsOnly( 0.0d ); } - @Test - public void shouldVoidMapintWhenReturnNull() { + @ProcessorTest + public void shouldVoidMapIntWhenReturnNull() { long[] existingTarget = new long[]{ 5L }; ScienceMapper.INSTANCE.nvmMappingVoidReturnNull( null, existingTarget ); - assertThat( existingTarget ).containsOnly( new long[] { 5L } ); + assertThat( existingTarget ).containsOnly( 5L ); } - @Test - public void shouldVoidMapintWhenReturnDefault() { + @ProcessorTest + public void shouldVoidMapIntWhenReturnDefault() { long[] existingTarget = new long[]{ 5L }; ScienceMapper.INSTANCE.nvmMappingVoidReturnDefault( null, existingTarget ); - assertThat( existingTarget ).containsOnly( new long[] { 0L } ); + assertThat( existingTarget ).containsOnly( 0L ); } + @ProcessorTest + @IssueKey( "999" ) + public void shouldNotContainFQNForStringArray() { + generatedSource.forMapper( ScienceMapper.class ).content().doesNotContain( "java.lang.String[]" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/ScienceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/array/ScienceMapper.java index 8c4936aa74..125dc94c9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/array/ScienceMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/array/ScienceMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.array; @@ -24,7 +11,9 @@ import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.array._target.GenericScientistDto; import org.mapstruct.ap.test.array._target.ScientistDto; +import org.mapstruct.ap.test.array.source.GenericScientist; import org.mapstruct.ap.test.array.source.Scientist; import org.mapstruct.factory.Mappers; @@ -35,14 +24,37 @@ public interface ScienceMapper { ScientistDto scientistToDto(Scientist scientist); - ScientistDto[] scientistsToDtos(Scientist[] scientists); + ScientistDto[] scientistsToDtosReturnNull(Scientist[] scientists); - ScientistDto[] scientistsToDtos(List scientists); + ScientistDto[] scientistsToDtosReturnNull(List scientists); - List scientistsToDtosAsList(Scientist[] scientists); + List scientistsToDtosAsListReturnNull(Scientist[] scientists); ScientistDto[] scientistsToDtos(Scientist[] scientists, @MappingTarget ScientistDto[] target); + @IterableMapping( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT ) + ScientistDto[] scientistsToDtosReturnDefault(Scientist[] scientists); + + @IterableMapping( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT ) + ScientistDto[] scientistsToDtosReturnDefault(List scientists); + + @IterableMapping( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT ) + List scientistsToDtosAsListReturnDefault(Scientist[] scientists); + + GenericScientistDto genericScientistToDto(GenericScientist scientist); + + GenericScientistDto[] genericScientistToDtosReturnNull(GenericScientist[] genericScientists); + + GenericScientistDto[] genericScientistToDtosReturnNull(List> genericScientists); + + List> genericScientistToDtosAsList(GenericScientist[] genericScientists); + + @IterableMapping( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT ) + GenericScientistDto[] genericScientistToDtosReturnDefault(GenericScientist[] genericScientists); + + @IterableMapping( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT ) + GenericScientistDto[] genericScientistToDtosReturnDefault(List> genericScientists); + @IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) boolean[] nvmMapping(boolean[] source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/_target/GenericScientistDto.java b/processor/src/test/java/org/mapstruct/ap/test/array/_target/GenericScientistDto.java new file mode 100644 index 0000000000..ffab268e61 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/array/_target/GenericScientistDto.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.ap.test.array._target; + +public class GenericScientistDto { + + //CHECKSTYLE:OFF + public T[] publicPublications; + public int[] publicPublicationYears; + //CHECKSTYLE:ON + + private T name; + private T[] publications; + private int[] publicationYears; + + public GenericScientistDto() { + } + + public GenericScientistDto(T name) { + this.name = name; + } + + public T getName() { + return name; + } + + public void setName(T name) { + this.name = name; + } + + public T[] getPublications() { + return publications; + } + + public void setPublications(T[] publications) { + this.publications = publications; + } + + public int[] getPublicationYears() { + return publicationYears; + } + + public void setPublicationYears(int[] publicationYears) { + this.publicationYears = publicationYears; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/_target/ScientistDto.java b/processor/src/test/java/org/mapstruct/ap/test/array/_target/ScientistDto.java index fcef7ffa9f..2fe85a14ff 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/array/_target/ScientistDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/array/_target/ScientistDto.java @@ -1,25 +1,17 @@ -/** - * Copyright 2012-2015 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.array._target; public class ScientistDto { + //CHECKSTYLE:OFF + public String[] publicPublications; + public int[] publicPublicationYears; + //CHECKSTYLE:ON + private String name; private String[] publications; private int[] publicationYears; diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/source/GenericScientist.java b/processor/src/test/java/org/mapstruct/ap/test/array/source/GenericScientist.java new file mode 100644 index 0000000000..6a6f9a8cda --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/array/source/GenericScientist.java @@ -0,0 +1,46 @@ +/* + * 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.test.array.source; + +public class GenericScientist { + + //CHECKSTYLE:OFF + public T[] publicPublications; + public T[] publicPublicationYears; + //CHECKSTYLE:ON + private T name; + private T[] publications; + private T[] publicationYears; + + public GenericScientist(T name) { + this.name = name; + } + + public T getName() { + return name; + } + + public void setName(T name) { + this.name = name; + } + + public T[] getPublications() { + return publications; + } + + public void setPublications(T[] publications) { + this.publications = publications; + } + + public T[] getPublicationYears() { + return publicationYears; + } + + public void setPublicationYears(T[] publicationYears) { + this.publicationYears = publicationYears; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/array/source/Scientist.java b/processor/src/test/java/org/mapstruct/ap/test/array/source/Scientist.java index cf720a0ba7..ac2f01f1d1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/array/source/Scientist.java +++ b/processor/src/test/java/org/mapstruct/ap/test/array/source/Scientist.java @@ -1,30 +1,20 @@ -/** - * Copyright 2012-2015 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.array.source; public class Scientist { + //CHECKSTYLE:OFF + public String[] publicPublications; + public String[] publicPublicationYears; + //CHECKSTYLE:ON private String name; private String[] publications; private String[] publicationYears; - public Scientist(String name) { this.name = name; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java index efcadb6a5e..9efb09b1e6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java @@ -1,29 +1,14 @@ -/** - * Copyright 2012-2015 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.bool; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @WithClasses({ Person.class, @@ -32,10 +17,9 @@ PersonMapper.class, YesNoMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class BooleanMappingTest { - @Test + @ProcessorTest public void shouldMapBooleanPropertyWithIsPrefixedGetter() { //given Person person = new Person(); @@ -48,7 +32,7 @@ public void shouldMapBooleanPropertyWithIsPrefixedGetter() { assertThat( personDto.getMarried() ).isEqualTo( "true" ); } - @Test + @ProcessorTest public void shouldMapBooleanPropertyPreferringGetPrefixedGetterOverIsPrefixedGetter() { //given Person person = new Person(); @@ -61,7 +45,7 @@ public void shouldMapBooleanPropertyPreferringGetPrefixedGetterOverIsPrefixedGet assertThat( personDto.getEngaged() ).isEqualTo( "true" ); } - @Test + @ProcessorTest public void shouldMapBooleanPropertyWithPropertyMappingMethod() { // given Person person = new Person(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bool/Person.java index f919c96feb..6b20598ea4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bool/Person.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/Person.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bool; @@ -33,14 +20,16 @@ public void setMarried(Boolean married) { this.married = married; } - public Boolean isEngaged() { - return engaged != null && !engaged; - } - + // START: please note: deliberately ordered, first getEngaged, then isEngaged. public Boolean getEngaged() { return engaged; } + public Boolean isEngaged() { + return engaged != null && !engaged; + } + // END + public void setEngaged(Boolean engaged) { this.engaged = engaged; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/bool/PersonDto.java index 807bd42e9f..6240df6860 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bool/PersonDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/PersonDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bool; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bool/PersonMapper.java index 69a57aca6b..04f806b11f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bool/PersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/PersonMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bool; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/YesNo.java b/processor/src/test/java/org/mapstruct/ap/test/bool/YesNo.java index 12f03b9060..7bbdb67cd4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bool/YesNo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/YesNo.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bool; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bool/YesNoMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bool/YesNoMapper.java index c2cdb875f3..5cc1a4c78f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bool/YesNoMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bool/YesNoMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bool; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/AbstractEntity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/AbstractEntity.java new file mode 100644 index 0000000000..88eaeffbb3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/AbstractEntity.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.ap.test.bugs._1005; + +/** + * @author Filip Hrisafov + */ +public abstract class AbstractEntity implements HasPrimaryKey { + + private Integer key; + + public Integer getKey() { + return key; + } + + public void setKey(Integer key) { + this.key = key; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/HasKey.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/HasKey.java new file mode 100644 index 0000000000..e8c9b71ef9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/HasKey.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._1005; + +/** + * @author Filip Hrisafov + */ +public interface HasKey { + + Integer getKey(); + + void setKey(Integer key); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/HasPrimaryKey.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/HasPrimaryKey.java new file mode 100644 index 0000000000..a027a51cbd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/HasPrimaryKey.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.ap.test.bugs._1005; + +/** + * @author Filip Hrisafov + */ +public interface HasPrimaryKey extends HasKey { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousAbstractResultTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousAbstractResultTypeMapper.java new file mode 100644 index 0000000000..b44b4e1476 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousAbstractResultTypeMapper.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.ap.test.bugs._1005; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1005ErroneousAbstractResultTypeMapper { + + @BeanMapping(resultType = AbstractEntity.class) + HasKey map(OrderDto orderDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousAbstractReturnTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousAbstractReturnTypeMapper.java new file mode 100644 index 0000000000..a71a4d1417 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousAbstractReturnTypeMapper.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.ap.test.bugs._1005; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1005ErroneousAbstractReturnTypeMapper { + + AbstractEntity map(OrderDto orderDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousInterfaceResultTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousInterfaceResultTypeMapper.java new file mode 100644 index 0000000000..b84821dc86 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousInterfaceResultTypeMapper.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.ap.test.bugs._1005; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1005ErroneousInterfaceResultTypeMapper { + + @BeanMapping(resultType = HasPrimaryKey.class) + HasKey map(OrderDto orderDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousInterfaceReturnTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousInterfaceReturnTypeMapper.java new file mode 100644 index 0000000000..525dc8ee94 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005ErroneousInterfaceReturnTypeMapper.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.ap.test.bugs._1005; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1005ErroneousInterfaceReturnTypeMapper { + + HasKey map(OrderDto orderDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005Test.java new file mode 100644 index 0000000000..5198394eb1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005Test.java @@ -0,0 +1,77 @@ +/* + * 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.test.bugs._1005; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1005") +@WithClasses({ + AbstractEntity.class, + HasKey.class, + HasPrimaryKey.class, + Order.class, + OrderDto.class +}) +public class Issue1005Test { + + @WithClasses(Issue1005ErroneousAbstractResultTypeMapper.class) + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = Issue1005ErroneousAbstractResultTypeMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "The result type AbstractEntity may not be an abstract class nor interface.") + }) + public void shouldFailDueToAbstractResultType() { + } + + @WithClasses(Issue1005ErroneousAbstractReturnTypeMapper.class) + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = Issue1005ErroneousAbstractReturnTypeMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 16, + message = "The return type AbstractEntity is an abstract class or interface. " + + "Provide a non abstract / non interface result type or a factory method.") + }) + public void shouldFailDueToAbstractReturnType() { + } + + @WithClasses(Issue1005ErroneousInterfaceResultTypeMapper.class) + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = Issue1005ErroneousInterfaceResultTypeMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "The result type HasPrimaryKey may not be an abstract class nor interface.") + }) + public void shouldFailDueToInterfaceResultType() { + } + + @WithClasses(Issue1005ErroneousInterfaceReturnTypeMapper.class) + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = Issue1005ErroneousInterfaceReturnTypeMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 16, + message = "The return type HasKey is an abstract class or interface. " + + "Provide a non abstract / non interface result type or a factory method.") + }) + public void shouldFailDueToInterfaceReturnType() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Order.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Order.java new file mode 100644 index 0000000000..e9722e13ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Order.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.ap.test.bugs._1005; + +/** + * @author Filip Hrisafov + */ +public class Order extends AbstractEntity { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/OrderDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/OrderDto.java new file mode 100644 index 0000000000..5108bd07de --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/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.ap.test.bugs._1005; + +/** + * @author Filip Hrisafov + */ +public class OrderDto { + + private String key; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1029/ErroneousIssue1029Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1029/ErroneousIssue1029Mapper.java new file mode 100644 index 0000000000..65cc635043 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1029/ErroneousIssue1029Mapper.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.ap.test.bugs._1029; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.TreeMap; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface ErroneousIssue1029Mapper { + + /** + * reports unmapped properties 'lastUpdated' and 'computedMapping' + */ + Deck toDeck(DeckForm form); + + /** + * reports unmapped property 'lastUpdated'. Property 'outdated' is read-only anyway, but can still be ignored + * explicitly without raising any errors. + */ + @Mappings({ + @Mapping(target = "outdated", ignore = true), + @Mapping(target = "computedMapping", ignore = true), + @Mapping(target = "knownProp", ignore = true) + }) + Deck toDeckWithSomeIgnores(DeckForm form); + + /** + * reports unknown property 'unknownProp' as error. + */ + @Mapping(target = "unknownProp", ignore = true) + Deck toDeckWithUnknownProperty(DeckForm form); + + class DeckForm { + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + } + + class Deck { + private Long id; + + private LocalDateTime lastUpdated; + + private String knownProp; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getKnownProp() { + return knownProp; + } + + public void setKnownProp(String knownProp) { + this.knownProp = knownProp; + } + + public LocalDateTime getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(LocalDateTime lastUpdated) { + this.lastUpdated = lastUpdated; + } + + // COMPUTED getters + + public boolean isOutdated() { + long daysBetween = ChronoUnit.DAYS.between( lastUpdated, LocalDateTime.now() ); + return daysBetween > 30; + } + + public Map getComputedMapping() { + return new TreeMap(); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1029/Issue1029Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1029/Issue1029Test.java new file mode 100644 index 0000000000..c2a87b769a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1029/Issue1029Test.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.test.bugs._1029; + +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * Verifies that read-only properties can be explicitly mentioned as {@code ignored=true} without raising an error. + * + * @author Andreas Gudian + */ +@WithClasses(ErroneousIssue1029Mapper.class) +@IssueKey("1029") +public class Issue1029Test { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic(kind = Kind.WARNING, line = 26, type = ErroneousIssue1029Mapper.class, + message = "Unmapped target properties: \"knownProp, lastUpdated, computedMapping\"."), + @Diagnostic(kind = Kind.WARNING, line = 37, type = ErroneousIssue1029Mapper.class, + message = "Unmapped target property: \"lastUpdated\"."), + @Diagnostic(kind = Kind.ERROR, line = 42, type = ErroneousIssue1029Mapper.class, + message = "Unknown property \"unknownProp\" in result type ErroneousIssue1029Mapper.Deck. " + + "Did you mean \"knownProp\"?") + }) + public void reportsProperWarningsAndError() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1061/Issue1061Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1061/Issue1061Test.java new file mode 100644 index 0000000000..b060d9727f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1061/Issue1061Test.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.ap.test.bugs._1061; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1061") +@WithClasses(SourceTargetMapper.class) +public class Issue1061Test { + + @ProcessorTest + public void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1061/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1061/SourceTargetMapper.java new file mode 100644 index 0000000000..88bc5328ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1061/SourceTargetMapper.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.ap.test.bugs._1061; + +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(implementationPackage = "") +public interface SourceTargetMapper { + + List map(List strings); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.java new file mode 100644 index 0000000000..322034d590 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Mapper.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.test.bugs._1111; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface Issue1111Mapper { + + Issue1111Mapper INSTANCE = Mappers.getMapper( Issue1111Mapper.class ); + + Target toTarget(Source in); + + List list(List in); + + List> listList(List> in); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.java new file mode 100644 index 0000000000..f5efb21e3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1111/Issue1111Test.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.test.bugs._1111; + +import java.util.Arrays; +import java.util.List; + +import org.mapstruct.ap.test.bugs._1111.Issue1111Mapper.Source; +import org.mapstruct.ap.test.bugs._1111.Issue1111Mapper.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Sjaak Derksen + */ +@IssueKey( "1111") +@WithClasses({Issue1111Mapper.class}) +public class Issue1111Test { + + @ProcessorTest + public void shouldCompile() { + + List> source = Arrays.asList( Arrays.asList( new Source( "test" ) ) ); + + List> target = Issue1111Mapper.INSTANCE.listList( source ); + + assertThat( target ).hasSize( 1 ); + assertThat( target.get( 0 ) ).hasSize( 1 ); + assertThat( target.get( 0 ).get( 0 ) ).isInstanceOf( Target.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1124/Issue1124Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1124/Issue1124Mapper.java new file mode 100644 index 0000000000..cae987aabe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1124/Issue1124Mapper.java @@ -0,0 +1,64 @@ +/* + * 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.test.bugs._1124; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface Issue1124Mapper { + class Entity { + private Long id; + private Entity entity; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Entity getEntity() { + return entity; + } + + public void setEntity(Entity entity) { + this.entity = entity; + } + } + + class DTO { + private Long id; + private DTO entity; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public DTO getEntity() { + return entity; + } + + public void setEntity(DTO entity) { + this.entity = entity; + } + } + + class MappingContext { + } + + @Mapping(target = "id", source = "entity.id") + DTO map(Entity entity, @Context MappingContext context); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1124/Issue1124Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1124/Issue1124Test.java new file mode 100644 index 0000000000..aecc0a3a15 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1124/Issue1124Test.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.test.bugs._1124; + +import org.mapstruct.ap.test.bugs._1124.Issue1124Mapper.DTO; +import org.mapstruct.ap.test.bugs._1124.Issue1124Mapper.Entity; +import org.mapstruct.ap.test.bugs._1124.Issue1124Mapper.MappingContext; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Andreas Gudian + */ +@IssueKey("1124") +@WithClasses(Issue1124Mapper.class) +public class Issue1124Test { + @ProcessorTest + public void nestedPropertyWithContextCompiles() { + Entity entity = new Entity(); + + Entity subEntity = new Entity(); + subEntity.setId( 42L ); + entity.setEntity( subEntity ); + + DTO dto = Mappers.getMapper( Issue1124Mapper.class ).map( entity, new MappingContext() ); + + assertThat( dto.getId() ).isEqualTo( 42L ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java new file mode 100644 index 0000000000..350d95eebc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Mapper.java @@ -0,0 +1,86 @@ +/* + * 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.test.bugs._1130; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; +import org.mapstruct.TargetType; + +/** + * Test mapper similar to the one provided in the issue. + * + * @author Andreas Gudian + */ +@Mapper +public abstract class Issue1130Mapper { + static class AEntity { + private BEntity b; + + public BEntity getB() { + return b; + } + + public void setB(BEntity b) { + this.b = b; + } + } + + static class BEntity { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + static class ADto { + private BDto b; + + public BDto getB() { + return b; + } + + public void setB(BDto b) { + this.b = b; + } + } + + class BDto { + private final String passedViaConstructor; + private String id; + + BDto(String passedViaConstructor) { + this.passedViaConstructor = passedViaConstructor; + } + + String getPassedViaConstructor() { + return passedViaConstructor; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + abstract void mergeA(AEntity source, @MappingTarget ADto target); + + abstract void mergeB(BEntity source, @MappingTarget BDto target); + + @ObjectFactory + protected BDto createB(@TargetType Class clazz) { + return new BDto( "created by factory" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Test.java new file mode 100644 index 0000000000..d0f8a1d3a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1130/Issue1130Test.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.ap.test.bugs._1130; + +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.bugs._1130.Issue1130Mapper.ADto; +import org.mapstruct.ap.test.bugs._1130.Issue1130Mapper.AEntity; +import org.mapstruct.ap.test.bugs._1130.Issue1130Mapper.BEntity; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests that when calling an update method for a previously null property, the factory method is called even if that + * factory method has a {@link TargetType} annotation. + * + * @author Andreas Gudian + */ +@IssueKey("1130") +@WithClasses(Issue1130Mapper.class) +public class Issue1130Test { + @ProcessorTest + public void factoryMethodWithTargetTypeInUpdateMethods() { + AEntity aEntity = new AEntity(); + aEntity.setB( new BEntity() ); + + ADto aDto = new ADto(); + Mappers.getMapper( Issue1130Mapper.class ).mergeA( aEntity, aDto ); + + assertThat( aDto.getB() ).isNotNull(); + assertThat( aDto.getB().getPassedViaConstructor() ).isEqualTo( "created by factory" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131Mapper.java new file mode 100644 index 0000000000..0e82c0708b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131Mapper.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._1131; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public abstract class Issue1131Mapper { + public static final Issue1131Mapper INSTANCE = Mappers.getMapper( Issue1131Mapper.class ); + + public static final List CALLED_METHODS = new ArrayList<>(); + + public abstract void merge(Source source, @MappingTarget Target target); + + public abstract void mergeNested(List source, @MappingTarget List target); + + @ObjectFactory + protected Target.Nested create(Source.Nested source) { + CALLED_METHODS.add( "create(Source.Nested)" ); + return new Target.Nested( "from object factory" ); + } + + @ObjectFactory + protected Target.Nested createWithSource(Source source) { + throw new IllegalArgumentException( "Should not use create with source" ); + } + + @ObjectFactory + protected List createWithSourceList(List source) { + CALLED_METHODS.add( "create(List)" ); + List result = new ArrayList<>(); + result.add( new Target.Nested( "from createWithSourceList" ) ); + return result; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131MapperWithContext.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131MapperWithContext.java new file mode 100644 index 0000000000..3f3176b57a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131MapperWithContext.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.ap.test.bugs._1131; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ObjectFactory; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public abstract class Issue1131MapperWithContext { + public static final Issue1131MapperWithContext INSTANCE = Mappers.getMapper( Issue1131MapperWithContext.class ); + + public static class MappingContext { + private final List calledMethods = new ArrayList<>(); + + public Target.Nested create(Source.Nested source) { + calledMethods.add( "create(Source.Nested)" ); + return new Target.Nested( "from within @Context" ); + } + + public List create(List source) { + calledMethods.add( "create(List)" ); + if ( source == null ) { + return new ArrayList<>(); + } + else { + return new ArrayList<>( source.size() ); + } + } + + public List getCalledMethods() { + return calledMethods; + } + } + + public abstract void merge(Source source, @MappingTarget Target target, @Context MappingContext context); + + public abstract void merge(List source, @MappingTarget List target, + @Context MappingContext context); + + @ObjectFactory + protected Target.Nested create(Source.Nested source, @Context MappingContext context) { + return context.create( source ); + } + + @ObjectFactory + protected Target.Nested createWithSource(Source source, @Context MappingContext context) { + throw new IllegalArgumentException( "Should not use create with source" ); + } + + @ObjectFactory + protected List createWithSourceList(List source, @Context MappingContext context) { + return context.create( source ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131Test.java new file mode 100644 index 0000000000..7a8c2797d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Issue1131Test.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.ap.test.bugs._1131; + +import java.util.ArrayList; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1131") +@WithClasses({ + Issue1131Mapper.class, + Issue1131MapperWithContext.class, + Source.class, + Target.class +}) +public class Issue1131Test { + + @ProcessorTest + public void shouldUseCreateWithSourceNested() { + + Source source = new Source(); + source.setNested( new Source.Nested() ); + source.getNested().setProperty( "something" ); + source.setMoreNested( new ArrayList<>() ); + + Target target = new Target(); + + Issue1131Mapper.CALLED_METHODS.clear(); + Issue1131Mapper.INSTANCE.merge( source, target ); + + assertThat( target.getNested() ).isNotNull(); + assertThat( target.getNested().getProperty() ).isEqualTo( "something" ); + assertThat( target.getNested().getInternal() ).isEqualTo( "from object factory" ); + assertThat( Issue1131Mapper.CALLED_METHODS ).containsExactly( + "create(Source.Nested)", + "create(List)" + ); + } + + @ProcessorTest + public void shouldUseContextObjectFactory() { + + Source source = new Source(); + source.setNested( new Source.Nested() ); + source.getNested().setProperty( "something" ); + source.setMoreNested( new ArrayList<>() ); + + Target target = new Target(); + + Issue1131MapperWithContext.MappingContext context = new Issue1131MapperWithContext.MappingContext(); + Issue1131MapperWithContext.INSTANCE.merge( source, target, context ); + + assertThat( target.getNested() ).isNotNull(); + assertThat( target.getNested().getProperty() ).isEqualTo( "something" ); + assertThat( target.getNested().getInternal() ).isEqualTo( "from within @Context" ); + assertThat( context.getCalledMethods() ).containsExactly( + "create(Source.Nested)", + "create(List)" + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Source.java new file mode 100644 index 0000000000..38c5e28490 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Source.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.ap.test.bugs._1131; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Source { + + public static class Nested { + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + } + + private Nested nested; + private List moreNested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public List getMoreNested() { + return moreNested; + } + + public void setMoreNested(List moreNested) { + this.moreNested = moreNested; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Target.java new file mode 100644 index 0000000000..a0a6337bc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1131/Target.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.ap.test.bugs._1131; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Target { + + public static class Nested { + private final String internal; + private String property; + + public Nested(String internal) { + this.internal = internal; + } + + public String getInternal() { + return internal; + } + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + } + + private Nested nested; + private List moreNested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public List getMoreNested() { + return moreNested; + } + + public void setMoreNested(List moreNested) { + this.moreNested = moreNested; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Entity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Entity.java new file mode 100644 index 0000000000..2db0196cf5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Entity.java @@ -0,0 +1,102 @@ +/* + * 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.test.bugs._1148; + +/** + * @author Filip Hrisafov + */ +class Entity { + + static class NestedClient { + //CHECKSTYLE:OFF + public long id; + //CHECKSTYLE:ON + } + + static class Client { + + //CHECKSTYLE:OFF + public NestedClient nestedClient; + //CHECKSTYLE:ON + } + + static class Dto { + + //CHECKSTYLE:OFF + public long recipientId; + public long senderId; + public NestedDto nestedDto; + public NestedDto nestedDto2; + public ClientDto sameLevel; + public ClientDto sameLevel2; + public ClientDto level; + public ClientDto level2; + //CHECKSTYLE:ON + } + + static class ClientDto { + //CHECKSTYLE:OFF + public NestedDto client; + + //CHECKSTYLE:ON + ClientDto(NestedDto client) { + this.client = client; + } + } + + static class NestedDto { + //CHECKSTYLE:OFF + public long id; + //CHECKSTYLE:ON + + NestedDto(long id) { + this.id = id; + } + } + + //CHECKSTYLE:OFF + public Client client; + public Client client2; + public NestedClient nested; + public NestedClient nested2; + private Client recipient; + private Client sender; + private long id; + private long id2; + //CHECKSTYLE:OFF + + public Client getRecipient() { + return recipient; + } + + public void setRecipient(Client recipient) { + this.recipient = recipient; + } + + public Client getSender() { + return sender; + } + + public void setSender(Client sender) { + this.sender = sender; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getId2() { + return id2; + } + + public void setId2(long id2) { + this.id2 = id2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Issue1148Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Issue1148Mapper.java new file mode 100644 index 0000000000..cf2cfdfaf4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Issue1148Mapper.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.ap.test.bugs._1148; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1148Mapper { + + Issue1148Mapper INSTANCE = Mappers.getMapper( Issue1148Mapper.class ); + + @Mappings({ + @Mapping(target = "sender.nestedClient.id", source = "senderId"), + @Mapping(target = "recipient.nestedClient.id", source = "recipientId"), + @Mapping(target = "client.nestedClient.id", source = "sameLevel.client.id"), + @Mapping(target = "client2.nestedClient.id", source = "sameLevel2.client.id"), + @Mapping(target = "nested.id", source = "level.client.id"), + @Mapping(target = "nested2.id", source = "level2.client.id"), + @Mapping(target = "id", source = "nestedDto.id"), + @Mapping(target = "id2", source = "nestedDto2.id") + }) + Entity toEntity(Entity.Dto dto); + + @Mappings({ + @Mapping(target = "sender.nestedClient.id", source = "dto2.senderId"), + @Mapping(target = "recipient.nestedClient.id", source = "dto1.recipientId"), + @Mapping(target = "client.nestedClient.id", source = "dto1.sameLevel.client.id"), + @Mapping(target = "client2.nestedClient.id", source = "dto2.sameLevel2.client.id"), + @Mapping(target = "nested.id", source = "dto1.level.client.id"), + @Mapping(target = "nested2.id", source = "dto2.level2.client.id"), + @Mapping(target = "id", source = "dto1.nestedDto.id"), + @Mapping(target = "id2", source = "dto2.nestedDto2.id") + }) + Entity toEntity(Entity.Dto dto1, Entity.Dto dto2); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Issue1148Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Issue1148Test.java new file mode 100644 index 0000000000..a698cfbfc6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1148/Issue1148Test.java @@ -0,0 +1,141 @@ +/* + * 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.test.bugs._1148; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Entity.class, + Issue1148Mapper.class +}) +@IssueKey("1148") +public class Issue1148Test { + + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsNestedSource() { + Entity.Dto dto = new Entity.Dto(); + dto.nestedDto = new Entity.NestedDto( 30 ); + dto.nestedDto2 = new Entity.NestedDto( 40 ); + Entity entity = Issue1148Mapper.INSTANCE.toEntity( dto ); + + assertThat( entity.getId() ).isEqualTo( 30 ); + assertThat( entity.getId2() ).isEqualTo( 40 ); + } + + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsNestedTarget() { + Entity.Dto dto = new Entity.Dto(); + dto.recipientId = 10; + dto.senderId = 20; + Entity entity = Issue1148Mapper.INSTANCE.toEntity( dto ); + + assertThat( entity.getRecipient() ).isNotNull(); + assertThat( entity.getRecipient().nestedClient ).isNotNull(); + assertThat( entity.getRecipient().nestedClient.id ).isEqualTo( 10 ); + + assertThat( entity.getSender() ).isNotNull(); + assertThat( entity.getSender().nestedClient ).isNotNull(); + assertThat( entity.getSender().nestedClient.id ).isEqualTo( 20 ); + } + + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsSymmetric() { + Entity.Dto dto = new Entity.Dto(); + dto.sameLevel = new Entity.ClientDto(new Entity.NestedDto( 30 )); + dto.sameLevel2 = new Entity.ClientDto(new Entity.NestedDto( 40 )); + Entity entity = Issue1148Mapper.INSTANCE.toEntity( dto ); + + assertThat( entity.client ).isNotNull(); + assertThat( entity.client.nestedClient ).isNotNull(); + assertThat( entity.client.nestedClient.id ).isEqualTo( 30 ); + + assertThat( entity.client2 ).isNotNull(); + assertThat( entity.client2.nestedClient ).isNotNull(); + assertThat( entity.client2.nestedClient.id ).isEqualTo( 40 ); + } + + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsHalfSymmetric() { + Entity.Dto dto = new Entity.Dto(); + dto.level = new Entity.ClientDto(new Entity.NestedDto( 80 )); + dto.level2 = new Entity.ClientDto(new Entity.NestedDto( 90 )); + Entity entity = Issue1148Mapper.INSTANCE.toEntity( dto ); + + assertThat( entity.nested ).isNotNull(); + assertThat( entity.nested.id ).isEqualTo( 80 ); + + assertThat( entity.nested2 ).isNotNull(); + assertThat( entity.nested2.id ).isEqualTo( 90 ); + } + + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsNestedSourceMultiple() { + Entity.Dto dto1 = new Entity.Dto(); + dto1.nestedDto = new Entity.NestedDto( 30 ); + Entity.Dto dto2 = new Entity.Dto(); + dto2.nestedDto2 = new Entity.NestedDto( 40 ); + Entity entity = Issue1148Mapper.INSTANCE.toEntity( dto1, dto2 ); + + assertThat( entity.getId() ).isEqualTo( 30 ); + assertThat( entity.getId2() ).isEqualTo( 40 ); + } + + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsNestedTargetMultiple() { + Entity.Dto dto1 = new Entity.Dto(); + dto1.recipientId = 10; + Entity.Dto dto2 = new Entity.Dto(); + dto2.senderId = 20; + Entity entity = Issue1148Mapper.INSTANCE.toEntity( dto1, dto2 ); + + assertThat( entity.getRecipient() ).isNotNull(); + assertThat( entity.getRecipient().nestedClient ).isNotNull(); + assertThat( entity.getRecipient().nestedClient.id ).isEqualTo( 10 ); + + assertThat( entity.getSender() ).isNotNull(); + assertThat( entity.getSender().nestedClient ).isNotNull(); + assertThat( entity.getSender().nestedClient.id ).isEqualTo( 20 ); + } + + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsSymmetricMultiple() { + Entity.Dto dto1 = new Entity.Dto(); + dto1.sameLevel = new Entity.ClientDto(new Entity.NestedDto( 30 )); + Entity.Dto dto2 = new Entity.Dto(); + dto2.sameLevel2 = new Entity.ClientDto(new Entity.NestedDto( 40 )); + Entity entity = Issue1148Mapper.INSTANCE.toEntity( dto1, dto2 ); + + assertThat( entity.client ).isNotNull(); + assertThat( entity.client.nestedClient ).isNotNull(); + assertThat( entity.client.nestedClient.id ).isEqualTo( 30 ); + + assertThat( entity.client2 ).isNotNull(); + assertThat( entity.client2.nestedClient ).isNotNull(); + assertThat( entity.client2.nestedClient.id ).isEqualTo( 40 ); + } + + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsHalfSymmetricMultiple() { + Entity.Dto dto1 = new Entity.Dto(); + dto1.level = new Entity.ClientDto(new Entity.NestedDto( 80 )); + Entity.Dto dto2 = new Entity.Dto(); + dto2.level2 = new Entity.ClientDto(new Entity.NestedDto( 90 )); + Entity entity = Issue1148Mapper.INSTANCE.toEntity( dto1, dto2 ); + + assertThat( entity.nested ).isNotNull(); + assertThat( entity.nested.id ).isEqualTo( 80 ); + + assertThat( entity.nested2 ).isNotNull(); + assertThat( entity.nested2.id ).isEqualTo( 90 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1153/ErroneousIssue1153Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1153/ErroneousIssue1153Mapper.java new file mode 100644 index 0000000000..71c8998a79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1153/ErroneousIssue1153Mapper.java @@ -0,0 +1,86 @@ +/* + * 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.test.bugs._1153; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousIssue1153Mapper { + + @Mappings( { + @Mapping( target = "readOnly", source = "nonNested"), + @Mapping( target = "nestedTarget.readOnly", source = "nestedSource.nested"), + @Mapping( target = "nestedTarget.writable", source = "nestedSource.writable"), + @Mapping( target = "nestedTarget2.readOnly", ignore = true), + @Mapping( target = "nestedTarget2.writable2", source = "nestedSource.writable"), + } ) + Target map(Source source); + + class Source { + + public static class NestedSource { + //CHECKSTYLE:OFF + public String nested; + public String writable; + //CHECKSTYLE:ON + } + + //CHECKSTYLE:OFF + public String nonNested; + public NestedSource nestedSource; + public NestedSource nestedSource2; + //CHECKSTYLE:ON + } + + class Target { + + public static class NestedTarget { + private String readOnly; + private String writable; + + public String getReadOnly() { + return readOnly; + } + + public String getWritable() { + return writable; + } + + public void setWritable(String writable) { + this.writable = writable; + } + } + + private String readOnly; + private NestedTarget nestedTarget; + private NestedTarget nestedTarget2; + + public String getReadOnly() { + return readOnly; + } + + public NestedTarget getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(NestedTarget nestedTarget) { + this.nestedTarget = nestedTarget; + } + + public NestedTarget getNestedTarget2() { + return nestedTarget2; + } + + public void setNestedTarget2(NestedTarget nestedTarget2) { + this.nestedTarget2 = nestedTarget2; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1153/Issue1153Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1153/Issue1153Test.java new file mode 100644 index 0000000000..5ceb1bb715 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1153/Issue1153Test.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.ap.test.bugs._1153; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@WithClasses(ErroneousIssue1153Mapper.class) +@IssueKey("1153") +public class Issue1153Test { + + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousIssue1153Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Property \"readOnly\" has no write accessor in ErroneousIssue1153Mapper.Target."), + @Diagnostic(type = ErroneousIssue1153Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Property \"readOnly\" has no write accessor in ErroneousIssue1153Mapper.Target.NestedTarget " + + "for target name \"nestedTarget.readOnly\"."), + @Diagnostic(type = ErroneousIssue1153Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "Unknown property \"writable2\" in type ErroneousIssue1153Mapper.Target.NestedTarget " + + "for target name \"nestedTarget2.writable2\". Did you mean \"nestedTarget2.writable\"?") + }) + @ProcessorTest + public void shouldReportErrorsCorrectly() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Entity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Entity.java new file mode 100644 index 0000000000..08617d6462 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Entity.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.test.bugs._1155; + +/** + * @author Filip Hrisafov + */ +class Entity { + + static class Client { + + //CHECKSTYLE:OFF + public long id; + //CHECKSTYLE:ON + } + + static class Dto { + + //CHECKSTYLE:OFF + public long clientId; + //CHECKSTYLE:ON + } + + //CHECKSTYLE:OFF + public Client client; + //CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Issue1155Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Issue1155Mapper.java new file mode 100644 index 0000000000..072121bf79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Issue1155Mapper.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.ap.test.bugs._1155; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1155Mapper { + + Issue1155Mapper INSTANCE = Mappers.getMapper( Issue1155Mapper.class ); + + @Mapping(target = "client.id", source = "clientId") + Entity toEntity(Entity.Dto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Issue1155Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Issue1155Test.java new file mode 100644 index 0000000000..9a6bb40ed1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1155/Issue1155Test.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.test.bugs._1155; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Entity.class, + Issue1155Mapper.class +}) +@IssueKey("1155") +public class Issue1155Test { + + @ProcessorTest + public void shouldCompile() { + + Entity.Dto dto = new Entity.Dto(); + dto.clientId = 10; + Entity entity = Issue1155Mapper.INSTANCE.toEntity( dto ); + + assertThat( entity.client ).isNotNull(); + assertThat( entity.client.id ).isEqualTo( 10 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/FaultyAstModifyingAnnotationProcessor.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/FaultyAstModifyingAnnotationProcessor.java new file mode 100644 index 0000000000..99d88e9883 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/FaultyAstModifyingAnnotationProcessor.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.ap.test.bugs._1159; + +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +/** + * @author Filip Hrisafov + */ +public class FaultyAstModifyingAnnotationProcessor implements AstModifyingAnnotationProcessor { + + public FaultyAstModifyingAnnotationProcessor() { + throw new RuntimeException( "Faulty AstModifyingAnnotationProcessor should not be instantiated" ); + } + + @Override + public boolean isTypeComplete(TypeMirror type) { + return false; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/Issue1159Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/Issue1159Mapper.java new file mode 100644 index 0000000000..ab1441c142 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/Issue1159Mapper.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.ap.test.bugs._1159; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1159Mapper { + + Issue1159Mapper INSTANCE = Mappers.getMapper( Issue1159Mapper.class ); + + CarManualDto translateManual(CarManual manual); + + /** + * @author Filip Hrisafov + */ + class CarManual { + + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } + + /** + * @author Filip Hrisafov + */ + class CarManualDto { + + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/Issue1159Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/Issue1159Test.java new file mode 100644 index 0000000000..7740662632 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1159/Issue1159Test.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.ap.test.bugs._1159; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1159") +@WithClasses({ + Issue1159Mapper.class, +}) +@WithServiceImplementation( + provides = AstModifyingAnnotationProcessor.class, + value = FaultyAstModifyingAnnotationProcessor.class +) +public class Issue1159Test { + + @ProcessorTest(Compiler.JDK) + // The warning is not present in the Eclipse compilation for some reason + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + messageRegExp = "Failed to read AstModifyingAnnotationProcessor. Reading next processor. Reason:.*" + + "Faulty AstModifyingAnnotationProcessor should not be instantiated" + ) + }) + public void shouldIgnoreFaultyAstModifyingProcessor() { + + Issue1159Mapper.CarManual manual = new Issue1159Mapper.CarManual(); + manual.setContent( "test" ); + + Issue1159Mapper.CarManualDto manualDto = Issue1159Mapper.INSTANCE.translateManual( manual ); + + assertThat( manualDto ).isNotNull(); + assertThat( manualDto.getContent() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/GenericHolder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/GenericHolder.java new file mode 100644 index 0000000000..6ebf510872 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/GenericHolder.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.ap.test.bugs._1164; + +/** + * @author Filip Hrisafov + */ +class GenericHolder { + + private T value; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Issue1164Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Issue1164Test.java new file mode 100644 index 0000000000..6db73113b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Issue1164Test.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.ap.test.bugs._1164; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@WithClasses( { + Source.class, + Target.class, + GenericHolder.class, + SourceTargetMapper.class +} ) +@IssueKey( "1164" ) +public class Issue1164Test { + + @ProcessorTest + public void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Source.java new file mode 100644 index 0000000000..7f72633739 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Source.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._1164; + +import java.util.List; +import java.util.Map; + +/** + * @author Filip Hrisafov + */ +class Source { + + public static class SourceNested { + } + + private List> nestedLists; + private Map> nestedMaps; + private GenericHolder> genericHolder; + + public List> getNestedLists() { + return nestedLists; + } + + public void setNestedLists(List> nestedLists) { + this.nestedLists = nestedLists; + } + + public Map> getNestedMaps() { + return nestedMaps; + } + + public void setNestedMaps(Map> nestedMaps) { + this.nestedMaps = nestedMaps; + } + + public GenericHolder> getGenericHolder() { + return genericHolder; + } + + public void setGenericHolder(GenericHolder> genericHolder) { + this.genericHolder = genericHolder; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/SourceTargetMapper.java new file mode 100644 index 0000000000..31cb10ba6f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/SourceTargetMapper.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.test.bugs._1164; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public abstract class SourceTargetMapper { + + public abstract Target map(Source source); + + protected List> mapLists(List> lists) { + return new ArrayList<>(); + } + + protected Map> map(Map> map) { + return new HashMap<>(); + } + + protected GenericHolder> map(GenericHolder> genericHolder) { + return new GenericHolder<>(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Target.java new file mode 100644 index 0000000000..103a699a1f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Target.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.ap.test.bugs._1164; + +import java.util.List; +import java.util.Map; + +/** + * @author Filip Hrisafov + */ +class Target { + + public static class TargetNested { + } + + public static class MapNested { + } + + public static class GenericNested { + } + + private List> nestedLists; + private Map> nestedMaps; + private GenericHolder> genericHolder; + + public List> getNestedLists() { + return nestedLists; + } + + public void setNestedLists(List> nestedLists) { + this.nestedLists = nestedLists; + } + + public Map> getNestedMaps() { + return nestedMaps; + } + + public void setNestedMaps(Map> nestedMaps) { + this.nestedMaps = nestedMaps; + } + + public GenericHolder> getGenericHolder() { + return genericHolder; + } + + public void setGenericHolder(GenericHolder> genericHolder) { + this.genericHolder = genericHolder; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/AdderSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/AdderSourceTargetMapper.java new file mode 100644 index 0000000000..e882421e9d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/AdderSourceTargetMapper.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.ap.test.bugs._1170; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._1170._target.Target; +import org.mapstruct.ap.test.bugs._1170.source.Source; +import org.mapstruct.factory.Mappers; + +/** + * @author Cornelius Dirmeier + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED, + uses = { PetMapper.class }) +public interface AdderSourceTargetMapper { + + AdderSourceTargetMapper INSTANCE = Mappers.getMapper( AdderSourceTargetMapper.class ); + + Target toTarget(Source source); + + Source toSource(Target source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/AdderTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/AdderTest.java new file mode 100644 index 0000000000..88576ba711 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/AdderTest.java @@ -0,0 +1,69 @@ +/* + * 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.test.bugs._1170; + +import java.util.Arrays; + +import org.assertj.core.api.ListAssert; +import org.mapstruct.ap.test.bugs._1170._target.Target; +import org.mapstruct.ap.test.bugs._1170.source.Source; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Cornelius Dirmeier + */ +@WithClasses({ + Source.class, + Target.class, + AdderSourceTargetMapper.class, + PetMapper.class +}) +public class AdderTest { + + @IssueKey("1170") + @ProcessorTest + public void testWildcardAdder() { + Source source = new Source(); + source.addWithoutWildcard( "mouse" ); + source.addWildcardInTarget( "mouse" ); + source.addWildcardInSource( "mouse" ); + source.addWildcardInBoth( "mouse" ); + source.addWildcardAdderToSetter( "mouse" ); + + Target target = AdderSourceTargetMapper.INSTANCE.toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getWithoutWildcards() ).containsExactly( 2L ); + assertThat( target.getWildcardInSources() ).containsExactly( 2L ); + ( (ListAssert) assertThat( target.getWildcardInTargets() ) ).containsExactly( 2L ); + ( (ListAssert) assertThat( target.getWildcardInBoths() ) ).containsExactly( 2L ); + assertThat( target.getWildcardAdderToSetters() ).containsExactly( 2L ); + } + + @IssueKey("1170") + @ProcessorTest + public void testWildcardAdderTargetToSource() { + Target target = new Target(); + target.addWithoutWildcard( 2L ); + target.addWildcardInTarget( 2L ); + target.getWildcardInSources().add( 2L ); + target.addWildcardInBoth( 2L ); + target.setWildcardAdderToSetters( Arrays.asList( 2L ) ); + + Source source = AdderSourceTargetMapper.INSTANCE.toSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getWithoutWildcards() ).containsExactly( "mouse" ); + ( (ListAssert) assertThat( source.getWildcardInSources() ) ).containsExactly( "mouse" ); + assertThat( source.getWildcardInTargets() ).containsExactly( "mouse" ); + ( (ListAssert) assertThat( source.getWildcardInBoths() ) ).containsExactly( "mouse" ); + ( (ListAssert) assertThat( source.getWildcardAdderToSetters() ) ).containsExactly( "mouse" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/PetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/PetMapper.java new file mode 100644 index 0000000000..10acf7f9c5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/PetMapper.java @@ -0,0 +1,65 @@ +/* + * 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.test.bugs._1170; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableMap; +import org.mapstruct.Mapper; + +/** + * @author Cornelius Dirmeier + */ +@Mapper +public class PetMapper { + + private static final Map PETS_TO_TARGET = ImmutableMap.builder() + .put( "rabbit", 1L ) + .put( "mouse", 2L ).build(); + + private static final Map PETS_TO_SOURCE = ImmutableMap.builder() + .put( 1L, "rabbit" ) + .put( 2L, "mouse" ) + .put( 3L, "cat" ) + .put( 4L, "dog" ).build(); + + /** + * method to be used when using an adder + * + * @param pet + * + * @return + */ + public Long toPet(String pet) { + return PETS_TO_TARGET.get( pet ); + } + + public String toSourcePets(Long pet) { + return PETS_TO_SOURCE.get( pet ); + } + + /** + * Method to be used when not using an adder + * + * @param pets + * + * @return + */ + public List toPets(List pets) { + return pets.stream() + .map( this::toPet ) + .collect( Collectors.toList() ); + } + + public List toSourcePets(List pets) { + return pets.stream() + .map( PETS_TO_SOURCE::get ) + .collect( Collectors.toList() ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/_target/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/_target/Target.java new file mode 100644 index 0000000000..5103fb075a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/_target/Target.java @@ -0,0 +1,107 @@ +/* + * 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.test.bugs._1170._target; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Cornelius Dirmeier + */ +public class Target { + + private List withoutWildcards = new ArrayList<>(); + + private List wildcardInSources = new ArrayList<>(); + + private List wildcardInTargets = new ArrayList<>(); + + private List wildcardInBoths = new ArrayList<>(); + + private List wildcardAdderToSetters = new ArrayList<>(); + + private List wildcardInSourcesAddAll = new ArrayList<>(); + + private List sameTypeWildcardInSources = new ArrayList<>(); + + private List sameTypeWildcardInTargets = new ArrayList<>(); + + private List sameTypeWildcardInBoths = new ArrayList<>(); + + public List getWithoutWildcards() { + return withoutWildcards; + } + + public Long addWithoutWildcard(Long pet) { + withoutWildcards.add( pet ); + return pet; + } + + public List getWildcardInSources() { + return wildcardInSources; + } + + public Long addWildcardInSource(Long pet) { + wildcardInSources.add( pet ); + return pet; + } + + public List getWildcardInTargets() { + return wildcardInTargets; + } + + public Long addWildcardInTarget(Long pet) { + wildcardInTargets.add( pet ); + return pet; + } + + public List getWildcardInBoths() { + return wildcardInTargets; + } + + public Long addWildcardInBoth(Long pet) { + wildcardInBoths.add( pet ); + return pet; + } + + public List getWildcardAdderToSetters() { + return wildcardAdderToSetters; + } + + public void setWildcardAdderToSetters(List pets) { + wildcardAdderToSetters = pets; + } + + public List getWildcardInSourcesAddAll() { + return wildcardInSourcesAddAll; + } + + public List getSameTypeWildcardInSources() { + return sameTypeWildcardInSources; + } + + public void addSameTypeWildcardInSource(BigDecimal pet) { + sameTypeWildcardInSources.add( pet ); + } + + public List getSameTypeWildcardInTargets() { + return sameTypeWildcardInTargets; + } + + public void addSameTypeWildcardInTarget(BigDecimal pet) { + sameTypeWildcardInTargets.add( pet ); + } + + public List getSameTypeWildcardInBoths() { + return sameTypeWildcardInBoths; + } + + public void addSameTypeWildcardInBoth(BigDecimal pet) { + sameTypeWildcardInBoths.add( pet ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/source/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/source/Source.java new file mode 100644 index 0000000000..989ef5f6b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1170/source/Source.java @@ -0,0 +1,107 @@ +/* + * 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.test.bugs._1170.source; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Cornelius Dirmeier + */ +public class Source { + + private List withoutWildcards = new ArrayList<>(); + + private List wildcardInSources = new ArrayList<>(); + + private List wildcardInTargets = new ArrayList<>(); + + private List wildcardInBoths = new ArrayList<>(); + + private List wildcardInSourcesAddAll = new ArrayList<>(); + + private List wildcardAdderToSetters = new ArrayList<>(); + + private List sameTypeWildcardInSources = new ArrayList<>(); + + private List sameTypeWildcardInTargets = new ArrayList<>(); + + private List sameTypeWildcardInBoths = new ArrayList<>(); + + public List getWithoutWildcards() { + return withoutWildcards; + } + + public void addWithoutWildcard(String pet) { + this.withoutWildcards.add( pet ); + } + + public List getWildcardInSources() { + return wildcardInSources; + } + + public void addWildcardInSource(String pet) { + wildcardInSources.add( pet ); + } + + public List getWildcardInTargets() { + return wildcardInTargets; + } + + public void addWildcardInTarget(String pet) { + wildcardInTargets.add( pet ); + } + + public List getWildcardInBoths() { + return wildcardInBoths; + } + + public void addWildcardInBoth(String pet) { + wildcardInBoths.add( pet ); + } + + public List getWildcardInSourcesAddAll() { + return wildcardInSourcesAddAll; + } + + public void addWildcardInSourcesAddAll(String pet) { + wildcardInSourcesAddAll.add( pet ); + } + + public List getWildcardAdderToSetters() { + return wildcardAdderToSetters; + } + + public void addWildcardAdderToSetter(String pet) { + wildcardAdderToSetters.add( pet ); + } + + public List getSameTypeWildcardInSources() { + return sameTypeWildcardInSources; + } + + public void addSameTypeWildcardInSource(BigDecimal pet) { + sameTypeWildcardInSources.add( pet ); + } + + public List getSameTypeWildcardInTargets() { + return sameTypeWildcardInTargets; + } + + public void addSameTypeWildcardInTarget(BigDecimal pet) { + sameTypeWildcardInTargets.add( pet ); + } + + public List getSameTypeWildcardInBoths() { + return sameTypeWildcardInBoths; + } + + public void addSameTypeWildcardInBoth(BigDecimal pet) { + sameTypeWildcardInBoths.add( pet ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/ErroneousIssue1180Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/ErroneousIssue1180Mapper.java new file mode 100644 index 0000000000..1f22d34578 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/ErroneousIssue1180Mapper.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.ap.test.bugs._1180; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper( config = SharedConfig.class ) +public abstract class ErroneousIssue1180Mapper { + + @InheritConfiguration + public abstract Target map(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Issue1180Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Issue1180Test.java new file mode 100644 index 0000000000..865816d83e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Issue1180Test.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.test.bugs._1180; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Sjaak Derksen + */ +@WithClasses( { + Source.class, + Target.class, + SharedConfig.class, + ErroneousIssue1180Mapper.class +} ) +@IssueKey( "1180" ) +public class Issue1180Test { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousIssue1180Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 18, + messageRegExp = "No property named \"sourceProperty\\.nonExistant\" exists.*") + }) + public void shouldCompileButNotGiveNullPointer() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/SharedConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/SharedConfig.java new file mode 100644 index 0000000000..3afa895967 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/SharedConfig.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.ap.test.bugs._1180; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * + * @author Sjaak Derksen + */ +@MapperConfig +public interface SharedConfig { + + @Mappings({ + @Mapping(target = "targetProperty", source = "sourceProperty.nonExistant") + }) + Target map(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Source.java new file mode 100644 index 0000000000..5f21a327f4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Source.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.ap.test.bugs._1180; + +/** + * @author Sjaak Derksen + */ +public class Source { + + private String sourceProperty; + + public String getSourceProperty() { + return sourceProperty; + } + + public void setSourceProperty(String sourceProperty) { + this.sourceProperty = sourceProperty; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Target.java new file mode 100644 index 0000000000..5619562567 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1180/Target.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.ap.test.bugs._1180; + +/** + * @author Sjaak Derksen + */ +public class Target { + + private String targetProperty; + + public String getTargetProperty() { + return targetProperty; + } + + public void setTargetProperty(String targetProperty) { + this.targetProperty = targetProperty; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/Issue1215Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/Issue1215Test.java new file mode 100644 index 0000000000..e3c08488a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/Issue1215Test.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.ap.test.bugs._1215; + +import org.mapstruct.ap.test.bugs._1215.dto.EntityDTO; +import org.mapstruct.ap.test.bugs._1215.entity.AnotherTag; +import org.mapstruct.ap.test.bugs._1215.entity.Entity; +import org.mapstruct.ap.test.bugs._1215.entity.Tag; +import org.mapstruct.ap.test.bugs._1215.mapper.Issue1215Mapper; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@WithClasses( { + EntityDTO.class, + Entity.class, + Tag.class, + AnotherTag.class, + Issue1215Mapper.class +} ) +@IssueKey( "1215" ) +public class Issue1215Test { + + @ProcessorTest + public void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/dto/EntityDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/dto/EntityDTO.java new file mode 100644 index 0000000000..68267dd30e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/dto/EntityDTO.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.ap.test.bugs._1215.dto; + +import org.mapstruct.ap.test.bugs._1215.entity.AnotherTag; +import org.mapstruct.ap.test.bugs._1215.entity.Tag; + +/** + * @author Filip Hrisafov + */ +public class EntityDTO { + private Tag[] tags; + private AnotherTag[][] otherTags; + + public Tag[] getTags() { + return tags; + } + + public void setTags(Tag[] tags) { + this.tags = tags; + } + + public AnotherTag[][] getOtherTags() { + return otherTags; + } + + public void setOtherTags(AnotherTag[][] otherTags) { + this.otherTags = otherTags; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/AnotherTag.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/AnotherTag.java new file mode 100644 index 0000000000..0938b1a783 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/AnotherTag.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.ap.test.bugs._1215.entity; + +/** + * @author Filip Hrisafov + */ +public class AnotherTag { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/Entity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/Entity.java new file mode 100644 index 0000000000..37ae4e1038 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/Entity.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.test.bugs._1215.entity; + +/** + * @author Filip Hrisafov + */ +public class Entity { + private Tag[] tags; + private AnotherTag[][] otherTags; + + public Tag[] getTags() { + return tags; + } + + public void setTags(Tag[] tags) { + this.tags = tags; + } + + public AnotherTag[][] getOtherTags() { + return otherTags; + } + + public void setOtherTags(AnotherTag[][] otherTags) { + this.otherTags = otherTags; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/Tag.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/Tag.java new file mode 100644 index 0000000000..04729f35b5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/entity/Tag.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.ap.test.bugs._1215.entity; + +/** + * @author Filip Hrisafov + */ +public class Tag { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/mapper/Issue1215Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/mapper/Issue1215Mapper.java new file mode 100644 index 0000000000..979b6f51c4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1215/mapper/Issue1215Mapper.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.ap.test.bugs._1215.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._1215.dto.EntityDTO; +import org.mapstruct.ap.test.bugs._1215.entity.Entity; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1215Mapper { + Entity fromDTO(EntityDTO dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/Issue1227Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/Issue1227Mapper.java new file mode 100644 index 0000000000..e34174f27b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/Issue1227Mapper.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.ap.test.bugs._1227; + +import java.lang.management.ThreadInfo; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1227Mapper { + + ThreadDto map(ThreadInfo source, String s); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/Issue1227Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/Issue1227Test.java new file mode 100644 index 0000000000..f98f6de398 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/Issue1227Test.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.ap.test.bugs._1227; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1227") +@WithClasses({ + Issue1227Mapper.class, + ThreadDto.class +}) +public class Issue1227Test { + + @ProcessorTest + public void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/ThreadDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/ThreadDto.java new file mode 100644 index 0000000000..ff14066a72 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1227/ThreadDto.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.ap.test.bugs._1227; + +/** + * @author Filip Hrisafov + */ +public class ThreadDto { + + private String threadName; + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.java new file mode 100644 index 0000000000..7a8ad05542 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/ErroneousIssue1242MapperMultipleSources.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.ap.test.bugs._1242; + +import org.mapstruct.Mapper; +import org.mapstruct.ObjectFactory; +import org.mapstruct.ReportingPolicy; + +/** + * Results in an ambiguous factory method error, as there are two methods with matching source types available. + * + * @author Andreas Gudian + */ +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public abstract class ErroneousIssue1242MapperMultipleSources { + abstract TargetA toTargetA(SourceA source); + + abstract TargetB toTargetB(SourceB source); + + @ObjectFactory + protected TargetB anotherTargetBCreator(SourceB source) { + throw new RuntimeException( "never to be called" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.java new file mode 100644 index 0000000000..90ea60a2ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Mapper.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.ap.test.bugs._1242; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; + +/** + * Test mapper for properly resolving the best fitting factory method + * + * @author Andreas Gudian + */ +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public abstract class Issue1242Mapper { + abstract TargetA toTargetA(SourceA source); + + abstract TargetB toTargetB(SourceB source); + + abstract void mergeA(SourceA source, @MappingTarget TargetA target); + + abstract void mergeB(SourceB source, @MappingTarget TargetB target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.java new file mode 100644 index 0000000000..036a63f312 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/Issue1242Test.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.ap.test.bugs._1242; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests that if multiple factory methods are applicable but only one of them has a source parameter, the one with the + * source param is chosen. + * + * @author Andreas Gudian + */ +@IssueKey("1242") +@WithClasses({ + Issue1242Mapper.class, + SourceA.class, + SourceB.class, + TargetA.class, + TargetB.class, + TargetFactories.class +}) +public class Issue1242Test { + @ProcessorTest + public void factoryMethodWithSourceParamIsChosen() { + SourceA sourceA = new SourceA(); + sourceA.setB( new SourceB() ); + + TargetA targetA = new TargetA(); + Mappers.getMapper( Issue1242Mapper.class ).mergeA( sourceA, targetA ); + + assertThat( targetA.getB() ).isNotNull(); + assertThat( targetA.getB().getPassedViaConstructor() ).isEqualTo( "created by factory" ); + + targetA = Mappers.getMapper( Issue1242Mapper.class ).toTargetA( sourceA ); + + assertThat( targetA.getB() ).isNotNull(); + assertThat( targetA.getB().getPassedViaConstructor() ).isEqualTo( "created by factory" ); + } + + @ProcessorTest + @WithClasses(ErroneousIssue1242MapperMultipleSources.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousIssue1242MapperMultipleSources.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 21, + message = "Ambiguous factory methods found for creating TargetB: " + + "TargetB anotherTargetBCreator(SourceB source), " + + "TargetB TargetFactories.createTargetB(SourceB source, @TargetType Class clazz), " + + "TargetB TargetFactories.createTargetB(@TargetType Class clazz), " + + "TargetB TargetFactories.createTargetB(). " + + "See https://mapstruct.org/faq/#ambiguous for more info.") + }) + public void ambiguousMethodErrorForTwoFactoryMethodsWithSourceParam() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/SourceA.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/SourceA.java new file mode 100644 index 0000000000..5afee73549 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/SourceA.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.ap.test.bugs._1242; + +/** + * @author Andreas Gudian + */ +class SourceA { + private SourceB b; + + public SourceB getB() { + return b; + } + + public void setB(SourceB b) { + this.b = b; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/SourceB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/SourceB.java new file mode 100644 index 0000000000..53a16640db --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/SourceB.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.ap.test.bugs._1242; + +/** + * @author Andreas Gudian + */ +class SourceB { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetA.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetA.java new file mode 100644 index 0000000000..1b861c8029 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetA.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.ap.test.bugs._1242; + +/** + * @author Andreas Gudian + */ +class TargetA { + private TargetB b; + + public TargetB getB() { + return b; + } + + public void setB(TargetB b) { + this.b = b; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java new file mode 100644 index 0000000000..6a28adc828 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.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.test.bugs._1242; + +/** + * @author Andreas Gudian + */ +class TargetB { + protected String value; + private final String passedViaConstructor; + + TargetB(String passedViaConstructor) { + this.passedViaConstructor = passedViaConstructor; + } + + String getPassedViaConstructor() { + return passedViaConstructor; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetFactories.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetFactories.java new file mode 100644 index 0000000000..bdec6b5ed8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetFactories.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.test.bugs._1242; + +import org.mapstruct.ObjectFactory; +import org.mapstruct.TargetType; + +/** + * Contains non-conflicting factory methods for {@link TargetB}. + * + * @author Andreas Gudian + */ +public class TargetFactories { + + @ObjectFactory + protected TargetB createTargetB(SourceB source, @TargetType Class clazz) { + return new TargetB( "created by factory" ); + } + + protected TargetB createTargetB(@TargetType Class clazz) { + throw new RuntimeException( "This method is not to be called" ); + } + + protected TargetB createTargetB() { + throw new RuntimeException( "This method is not to be called" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1244/Issue1244Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1244/Issue1244Test.java new file mode 100644 index 0000000000..231ff67334 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1244/Issue1244Test.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.test.bugs._1244; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1244") +@WithClasses( SizeMapper.class ) +public class Issue1244Test { + + @ProcessorTest + public void properlyCreatesMapperWithSizeAsParameterName() { + SizeMapper.SizeHolder sizeHolder = new SizeMapper.SizeHolder(); + sizeHolder.setSize( "size" ); + + SizeMapper.SizeHolderDto dto = Mappers.getMapper( SizeMapper.class ).convert( sizeHolder ); + assertThat( dto.getSize() ).isEqualTo( "size" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1244/SizeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1244/SizeMapper.java new file mode 100644 index 0000000000..0e647360e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1244/SizeMapper.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.ap.test.bugs._1244; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SizeMapper { + + SizeHolderDto convert(SizeHolder size); + + SizeHolderDto convert(SizeHolder size, int test); + + SizeHolderDto convertOther(SizeHolder sizeHolder, int size); + + class SizeHolder { + private String size; + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + } + + class SizeHolderDto { + private String size; + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/DtoIn.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/DtoIn.java new file mode 100644 index 0000000000..69a19454e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/DtoIn.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.ap.test.bugs._1247; + +/** + * @author Filip Hrisafov + */ +public class DtoIn { + + private final String data; + private final String data2; + + public DtoIn(String data, String data2) { + this.data = data; + this.data2 = data2; + } + + public String getData() { + return data; + } + + public String getData2() { + return data2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/DtoOut.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/DtoOut.java new file mode 100644 index 0000000000..667a5e02ad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/DtoOut.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.ap.test.bugs._1247; + +/** + * @author Filip Hrisafov + */ +public class DtoOut { + + private String data; + private InternalDto internal; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public InternalDto getInternal() { + return internal; + } + + public void setInternal(InternalDto internal) { + this.internal = internal; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/InternalData.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/InternalData.java new file mode 100644 index 0000000000..2c9684584f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/InternalData.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.ap.test.bugs._1247; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class InternalData { + + private List list; + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/InternalDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/InternalDto.java new file mode 100644 index 0000000000..a69385dd6f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/InternalDto.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.ap.test.bugs._1247; + +/** + * @author Filip Hrisafov + */ +public class InternalDto { + + private String data2; + private InternalData internalData; + + public String getData2() { + return data2; + } + + public void setData2(String data2) { + this.data2 = data2; + } + + public InternalData getInternalData() { + return internalData; + } + + public void setInternalData(InternalData internalData) { + this.internalData = internalData; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/Issue1247Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/Issue1247Mapper.java new file mode 100644 index 0000000000..5e216a334f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/Issue1247Mapper.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.test.bugs._1247; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1247Mapper { + + Issue1247Mapper INSTANCE = Mappers.getMapper( Issue1247Mapper.class ); + + @Mappings( { + @Mapping(target = "internal", source = "in"), + @Mapping(target = "internal.internalData.list", source = "list") + } ) + DtoOut map(DtoIn in, List list); + + @Mappings( { + @Mapping(target = "internal", source = "in"), + @Mapping(target = "internal.expression", expression = "java(\"testingExpression\")"), + @Mapping(target = "internal.internalData.list", source = "list"), + @Mapping(target = "internal.internalData.defaultValue", source = "in.data2", defaultValue = "missing"), + @Mapping(target = "constant", constant = "someConstant") + } ) + OtherDtoOut mapWithConstantExpressionAndDefault(DtoIn in, List list); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/Issue1247Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/Issue1247Test.java new file mode 100644 index 0000000000..1dd12d6e23 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/Issue1247Test.java @@ -0,0 +1,85 @@ +/* + * 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.test.bugs._1247; + +import java.util.Arrays; +import java.util.List; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1247") +@WithClasses({ + Issue1247Mapper.class, + DtoIn.class, + DtoOut.class, + InternalData.class, + InternalDto.class, + OtherDtoOut.class, + OtherInternalData.class, + OtherInternalDto.class +}) +public class Issue1247Test { + + @ProcessorTest + public void shouldCorrectlyUseMappings() { + + DtoIn in = new DtoIn( "data", "data2" ); + List list = Arrays.asList( "first", "second" ); + DtoOut out = Issue1247Mapper.INSTANCE.map( in, list ); + + assertThat( out ).isNotNull(); + assertThat( out.getData() ).isEqualTo( "data" ); + assertThat( out.getInternal() ).isNotNull(); + assertThat( out.getInternal().getData2() ).isEqualTo( "data2" ); + assertThat( out.getInternal().getInternalData() ).isNotNull(); + assertThat( out.getInternal().getInternalData().getList() ).containsExactly( "first", "second" ); + } + + @ProcessorTest + public void shouldCorrectlyUseMappingsWithConstantsExpressionsAndDefaults() { + + DtoIn in = new DtoIn( "data", "data2" ); + List list = Arrays.asList( "first", "second" ); + OtherDtoOut out = Issue1247Mapper.INSTANCE.mapWithConstantExpressionAndDefault( in, list ); + + assertThat( out ).isNotNull(); + assertThat( out.getData() ).isEqualTo( "data" ); + assertThat( out.getConstant() ).isEqualTo( "someConstant" ); + assertThat( out.getInternal() ).isNotNull(); + // This will not be mapped by the @Mapping(target = "internal", source = "in") because we have one more + // symmetric mapping @Mapping(target = "internal.expression", expression = "java(\"testingExpression\")") + assertThat( out.getInternal().getData2() ).isNull(); + assertThat( out.getInternal().getExpression() ).isEqualTo( "testingExpression" ); + assertThat( out.getInternal().getInternalData() ).isNotNull(); + assertThat( out.getInternal().getInternalData().getList() ).containsExactly( "first", "second" ); + assertThat( out.getInternal().getInternalData().getDefaultValue() ).isEqualTo( "data2" ); + } + + @ProcessorTest + public void shouldCorrectlyUseMappingsWithConstantsExpressionsAndUseDefault() { + + DtoIn in = new DtoIn( "data", null ); + List list = Arrays.asList( "first", "second" ); + OtherDtoOut out = Issue1247Mapper.INSTANCE.mapWithConstantExpressionAndDefault( in, list ); + + assertThat( out ).isNotNull(); + assertThat( out.getData() ).isEqualTo( "data" ); + assertThat( out.getConstant() ).isEqualTo( "someConstant" ); + assertThat( out.getInternal() ).isNotNull(); + assertThat( out.getInternal().getData2() ).isNull(); + assertThat( out.getInternal().getExpression() ).isEqualTo( "testingExpression" ); + assertThat( out.getInternal().getInternalData() ).isNotNull(); + assertThat( out.getInternal().getInternalData().getList() ).containsExactly( "first", "second" ); + assertThat( out.getInternal().getInternalData().getDefaultValue() ).isEqualTo( "missing" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherDtoOut.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherDtoOut.java new file mode 100644 index 0000000000..80660b0bf1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherDtoOut.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._1247; + +/** + * @author Filip Hrisafov + */ +public class OtherDtoOut { + + private String data; + private OtherInternalDto internal; + private String constant; + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public OtherInternalDto getInternal() { + return internal; + } + + public void setInternal(OtherInternalDto internal) { + this.internal = internal; + } + + public String getConstant() { + return constant; + } + + public void setConstant(String constant) { + this.constant = constant; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherInternalData.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherInternalData.java new file mode 100644 index 0000000000..3773029266 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherInternalData.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.ap.test.bugs._1247; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class OtherInternalData { + + private List list; + private String defaultValue; + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherInternalDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherInternalDto.java new file mode 100644 index 0000000000..25411c207c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1247/OtherInternalDto.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._1247; + +/** + * @author Filip Hrisafov + */ +public class OtherInternalDto { + + private String data2; + private String expression; + private OtherInternalData internalData; + + public String getData2() { + return data2; + } + + public void setData2(String data2) { + this.data2 = data2; + } + + public String getExpression() { + return expression; + } + + public void setExpression(String expression) { + this.expression = expression; + } + + public OtherInternalData getInternalData() { + return internalData; + } + + public void setInternalData(OtherInternalData internalData) { + this.internalData = internalData; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/AbstractA.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/AbstractA.java new file mode 100644 index 0000000000..edb36b1d38 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/AbstractA.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.ap.test.bugs._1255; + +/** + * + * @author Sjaak Derksen + */ +public abstract class AbstractA { + + private String field1; + + public String getField1() { + return field1; + } + + public void setField1(String field1) { + this.field1 = field1; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/Issue1255Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/Issue1255Test.java new file mode 100644 index 0000000000..9d27fdf681 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/Issue1255Test.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.ap.test.bugs._1255; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Sjaak Derksen + */ +@IssueKey("1255") +@WithClasses({ + AbstractA.class, + SomeA.class, + SomeB.class, + SomeMapper.class, + SomeMapperConfig.class}) +public class Issue1255Test { + + @ProcessorTest + public void shouldMapSomeBToSomeAWithoutField1() { + SomeB someB = new SomeB(); + someB.setField1( "value1" ); + someB.setField2( "value2" ); + + SomeA someA = SomeMapper.INSTANCE.toSomeA( someB ); + + assertThat( someA.getField1() ) + .isNotEqualTo( someB.getField1() ) + .isNull(); + assertThat( someA.getField2() ).isEqualTo( someB.getField2() ); + } + + @ProcessorTest + public void shouldMapSomeAToSomeB() { + SomeA someA = new SomeA(); + someA.setField1( "value1" ); + someA.setField2( "value2" ); + + SomeB someB = SomeMapper.INSTANCE.toSomeB( someA ); + + assertThat( someB.getField1() ).isEqualTo( someA.getField1() ); + assertThat( someB.getField2() ).isEqualTo( someA.getField2() ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeA.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeA.java new file mode 100644 index 0000000000..cea619e6fd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeA.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.ap.test.bugs._1255; + +/** + * + * @author Sjaak Derksen + */ +public class SomeA extends AbstractA { + + private String field2; + + public String getField2() { + return field2; + } + + public void setField2(String field2) { + this.field2 = field2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeB.java new file mode 100644 index 0000000000..9f6019fdee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeB.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._1255; + +/** + * + * @author Sjaak Derksen + */ +public class SomeB { + + private String field1; + private String field2; + + public String getField1() { + return field1; + } + + public void setField1(String field1) { + this.field1 = field1; + } + + public String getField2() { + return field2; + } + + public void setField2(String field2) { + this.field2 = field2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeMapper.java new file mode 100644 index 0000000000..b1350d991c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeMapper.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.ap.test.bugs._1255; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper(config = SomeMapperConfig.class) +public interface SomeMapper { + + SomeMapper INSTANCE = Mappers.getMapper( SomeMapper.class ); + + SomeA toSomeA(SomeB source); + + SomeB toSomeB(SomeA source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeMapperConfig.java new file mode 100644 index 0000000000..145de5b95c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1255/SomeMapperConfig.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.ap.test.bugs._1255; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; +import org.mapstruct.Mappings; + +/** + * + * @author Sjaak Derksen + */ +@MapperConfig( + mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG +) +public interface SomeMapperConfig { + + @Mappings({ + @Mapping(target = "field1", ignore = true) + }) + AbstractA toAbstractA(SomeB source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/Issue1269Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/Issue1269Test.java new file mode 100644 index 0000000000..668b9e5110 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/Issue1269Test.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.ap.test.bugs._1269; + +import java.util.Arrays; +import java.util.List; + +import org.mapstruct.ap.test.bugs._1269.dto.VehicleDto; +import org.mapstruct.ap.test.bugs._1269.dto.VehicleImageDto; +import org.mapstruct.ap.test.bugs._1269.dto.VehicleInfoDto; +import org.mapstruct.ap.test.bugs._1269.mapper.VehicleMapper; +import org.mapstruct.ap.test.bugs._1269.model.Vehicle; +import org.mapstruct.ap.test.bugs._1269.model.VehicleImage; +import org.mapstruct.ap.test.bugs._1269.model.VehicleTypeInfo; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey( "1269" ) +@WithClasses( { + VehicleDto.class, + VehicleImageDto.class, + VehicleInfoDto.class, + Vehicle.class, + VehicleImage.class, + VehicleTypeInfo.class, + VehicleMapper.class +} ) +public class Issue1269Test { + + @ProcessorTest + public void shouldMapNestedPropertiesCorrectly() { + + VehicleTypeInfo sourceTypeInfo = new VehicleTypeInfo( "Opel", "Corsa", 3 ); + + List sourceImages = Arrays.asList( + new VehicleImage( 100, "something" ), + new VehicleImage( 150, "somethingElse" ) + ); + Vehicle source = new Vehicle( sourceTypeInfo, sourceImages ); + + VehicleDto target = VehicleMapper.INSTANCE.map( source ); + + assertThat( target.getVehicleInfo() ).isNotNull(); + assertThat( target.getVehicleInfo().getDoors() ).isEqualTo( 3 ); + assertThat( target.getVehicleInfo().getType() ).isEqualTo( "Opel" ); + assertThat( target.getVehicleInfo().getName() ).isEqualTo( "Corsa" ); + assertThat( target.getVehicleInfo().getImages() ).hasSize( 2 ); + assertThat( target.getVehicleInfo().getImages().get( 0 ) ) + .isEqualToComparingFieldByField( sourceImages.get( 0 ) ); + assertThat( target.getVehicleInfo().getImages().get( 1 ) ) + .isEqualToComparingFieldByField( sourceImages.get( 1 ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleDto.java new file mode 100755 index 0000000000..bc6842cfef --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleDto.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.ap.test.bugs._1269.dto; + +/** + * @author Filip Hrisafov + */ +public class VehicleDto { + + private VehicleInfoDto vehicleInfo; + + public VehicleInfoDto getVehicleInfo() { + return vehicleInfo; + } + + public void setVehicleInfo(VehicleInfoDto vehicleInfo) { + this.vehicleInfo = vehicleInfo; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleImageDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleImageDto.java new file mode 100755 index 0000000000..3e0b0b8414 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleImageDto.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._1269.dto; + +/** + * @author Filip Hrisafov + */ +public class VehicleImageDto { + + private Integer pictureSize; + + private String src; + + public Integer getPictureSize() { + return pictureSize; + } + + public void setPictureSize(Integer pictureSize) { + this.pictureSize = pictureSize; + } + + public String getSrc() { + return src; + } + + public void setSrc(String src) { + this.src = src; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleInfoDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleInfoDto.java new file mode 100755 index 0000000000..48255fcb25 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/dto/VehicleInfoDto.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.test.bugs._1269.dto; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class VehicleInfoDto { + + private String type; + + private String name; + + private Integer doors; + + // make sure that mapping on images does not happen based on images mapping + private List images; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getDoors() { + return doors; + } + + public void setDoors(Integer doors) { + this.doors = doors; + } + + public List getImages() { + return images; + } + + public void setImages(List images) { + this.images = images; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/mapper/VehicleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/mapper/VehicleMapper.java new file mode 100755 index 0000000000..eba04c130b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/mapper/VehicleMapper.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.ap.test.bugs._1269.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.bugs._1269.dto.VehicleDto; +import org.mapstruct.ap.test.bugs._1269.model.Vehicle; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface VehicleMapper { + VehicleMapper INSTANCE = Mappers.getMapper( VehicleMapper.class ); + + @Mappings({ + @Mapping(target = "vehicleInfo", source = "vehicleTypeInfo"), + @Mapping(target = "vehicleInfo.images", source = "images") + }) + VehicleDto map(Vehicle in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/Vehicle.java new file mode 100755 index 0000000000..9b02435a16 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/Vehicle.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.ap.test.bugs._1269.model; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Vehicle { + + private final VehicleTypeInfo vehicleTypeInfo; + + private final List images; + + public Vehicle(VehicleTypeInfo vehicleTypeInfo, List images) { + this.vehicleTypeInfo = vehicleTypeInfo; + this.images = images; + } + + public VehicleTypeInfo getVehicleTypeInfo() { + return vehicleTypeInfo; + } + + public List getImages() { + return images; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/VehicleImage.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/VehicleImage.java new file mode 100755 index 0000000000..19ac16c8ad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/VehicleImage.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.ap.test.bugs._1269.model; + +/** + * @author Filip Hrisafov + */ +public class VehicleImage { + + private final Integer pictureSize; + + private final String src; + + public VehicleImage(Integer pictureSize, String src) { + this.pictureSize = pictureSize; + this.src = src; + } + + public Integer getPictureSize() { + return pictureSize; + } + + public String getSrc() { + return src; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/VehicleTypeInfo.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/VehicleTypeInfo.java new file mode 100755 index 0000000000..7310e63c3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1269/model/VehicleTypeInfo.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.test.bugs._1269.model; + +/** + * @author Filip Hrisafov + */ +public class VehicleTypeInfo { + + private final String type; + + private final String name; + + private final Integer doors; + + public VehicleTypeInfo(String type, String name, Integer doors) { + this.type = type; + this.name = name; + this.doors = doors; + } + + public String getType() { + return type; + } + + public String getName() { + return name; + } + + public Integer getDoors() { + return doors; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Dto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Dto.java new file mode 100644 index 0000000000..9c912c2210 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Dto.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.ap.test.bugs._1273; + +import java.util.List; + +public class Dto { + + List longs; + + public List getLongs() { + return longs; + } + + public void setLongs(List longs) { + this.longs = longs; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Entity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Entity.java new file mode 100644 index 0000000000..fa1c1d47f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Entity.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.ap.test.bugs._1273; + +import java.util.ArrayList; +import java.util.List; + +public class Entity { + + List longs = new ArrayList<>(); + + public List getLongs() { + return longs; + } + + public void setLongs(List longs) { + this.longs = longs; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnDefault.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnDefault.java new file mode 100644 index 0000000000..0b31e1d734 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnDefault.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.ap.test.bugs._1273; + +import java.util.ArrayList; + +import org.mapstruct.Mapper; +import org.mapstruct.ObjectFactory; + +@Mapper +public interface EntityMapperReturnDefault { + + Dto asTarget(Entity entity); + + @ObjectFactory + default Dto createDto() { + Dto result = new Dto(); + result.setLongs( new ArrayList<>( ) ); + return result; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnNull.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnNull.java new file mode 100644 index 0000000000..331de18d13 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/EntityMapperReturnNull.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._1273; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; + +@Mapper( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL ) +public interface EntityMapperReturnNull { + + Dto asTarget(Entity entity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Issue1273Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Issue1273Test.java new file mode 100644 index 0000000000..9874b33436 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1273/Issue1273Test.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._1273; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey( "1273" ) +@WithClasses( { EntityMapperReturnDefault.class, EntityMapperReturnNull.class, Dto.class, Entity.class } ) +public class Issue1273Test { + + @ProcessorTest + public void shouldCorrectlyMapCollectionWithNullValueMappingStrategyReturnDefault() { + EntityMapperReturnDefault entityMapper = Mappers.getMapper( EntityMapperReturnDefault.class ); + + Entity entity = createEntityWithEmptyList(); + + Dto dto = entityMapper.asTarget( entity ); + assertThat( dto.getLongs() ).isNotNull(); + } + + @ProcessorTest + public void shouldCorrectlyMapCollectionWithNullValueMappingStrategyReturnNull() { + EntityMapperReturnNull entityMapper = Mappers.getMapper( EntityMapperReturnNull.class ); + + Entity entity = createEntityWithEmptyList(); + + Dto dto = entityMapper.asTarget( entity ); + assertThat( dto.getLongs() ).isNull(); + } + + private Entity createEntityWithEmptyList() { + Entity entity = new Entity(); + entity.setLongs( null ); + + return entity; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/ErroneousInverseTargetHasNoSuitableConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/ErroneousInverseTargetHasNoSuitableConstructorMapper.java new file mode 100644 index 0000000000..fd109d0dc6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/ErroneousInverseTargetHasNoSuitableConstructorMapper.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.ap.test.bugs._1283; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousInverseTargetHasNoSuitableConstructorMapper { + + @Mapping(target = "target", source = "source") + Target fromSource(Source source); + + @InheritInverseConfiguration + Source fromTarget(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/ErroneousTargetHasNoSuitableConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/ErroneousTargetHasNoSuitableConstructorMapper.java new file mode 100644 index 0000000000..259285993c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/ErroneousTargetHasNoSuitableConstructorMapper.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.ap.test.bugs._1283; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousTargetHasNoSuitableConstructorMapper { + + @Mapping(target = "source", source = "target") + Source fromTarget(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Issue1283Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Issue1283Test.java new file mode 100644 index 0000000000..1879c9398b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Issue1283Test.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.ap.test.bugs._1283; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1283") +@WithClasses({ + Source.class, + Target.class +}) +public class Issue1283Test { + + @ProcessorTest + @WithClasses(ErroneousInverseTargetHasNoSuitableConstructorMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousInverseTargetHasNoSuitableConstructorMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 22L, + message = "Source does not have an accessible constructor." + ) + } + ) + public void inheritInverseConfigurationReturnTypeHasNoSuitableConstructor() { + } + + @ProcessorTest + @WithClasses(ErroneousTargetHasNoSuitableConstructorMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousTargetHasNoSuitableConstructorMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 18L, + message = "Source does not have an accessible constructor." + ) + } + ) + public void returnTypeHasNoSuitableConstructor() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Source.java new file mode 100644 index 0000000000..227296ad25 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Source.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.ap.test.bugs._1283; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String source; + + private Source(String source) { + this.source = source; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Target.java new file mode 100644 index 0000000000..02a26968f8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1283/Target.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.ap.test.bugs._1283; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String target; + + public String getTarget() { + return target; + } + + public void setTarget(String target) { + this.target = target; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Issue1320Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Issue1320Mapper.java new file mode 100644 index 0000000000..b20ef4a72d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Issue1320Mapper.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.ap.test.bugs._1320; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1320Mapper { + + Issue1320Mapper INSTANCE = Mappers.getMapper( Issue1320Mapper.class ); + + @Mappings({ + @Mapping(target = "address.city.cityName", constant = "myCity"), + @Mapping(target = "address.city.stateName", constant = "myState") + }) + Target map(Integer dummy); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Issue1320Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Issue1320Test.java new file mode 100644 index 0000000000..b84fe2d8e6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Issue1320Test.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.ap.test.bugs._1320; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1320") +@WithClasses({ + Issue1320Mapper.class, + Target.class +}) +public class Issue1320Test { + + @ProcessorTest + public void shouldCreateDeepNestedConstantsCorrectly() { + Target target = Issue1320Mapper.INSTANCE.map( 10 ); + + assertThat( target.getAddress() ).isNotNull(); + assertThat( target.getAddress().getCity() ).isNotNull(); + assertThat( target.getAddress().getCity().getCityName() ).isEqualTo( "myCity" ); + assertThat( target.getAddress().getCity().getStateName() ).isEqualTo( "myState" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Target.java new file mode 100644 index 0000000000..3fb27141eb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1320/Target.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.test.bugs._1320; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private Address address; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + + public static class Address { + private City city; + + public City getCity() { + return city; + } + + public void setCity(City city) { + this.city = city; + } + } + + public static class City { + private String cityName; + private String stateName; + + public String getCityName() { + return cityName; + } + + public void setCityName(String cityName) { + this.cityName = cityName; + } + + public String getStateName() { + return stateName; + } + + public void setStateName(String stateName) { + this.stateName = stateName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Issue1338Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Issue1338Mapper.java new file mode 100644 index 0000000000..faff474e00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Issue1338Mapper.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.ap.test.bugs._1338; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue1338Mapper { + + Issue1338Mapper INSTANCE = Mappers.getMapper( Issue1338Mapper.class ); + + Target map(Source source); + + Source map(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Issue1338Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Issue1338Test.java new file mode 100644 index 0000000000..924f5e8c86 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Issue1338Test.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.ap.test.bugs._1338; + +import java.util.Arrays; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1338") +@WithClasses({ + Issue1338Mapper.class, + Source.class, + Target.class +}) +public class Issue1338Test { + + @ProcessorTest + public void shouldCorrectlyUseAdder() { + Source source = new Source(); + source.setProperties( Arrays.asList( "first", "second" ) ); + Target target = Issue1338Mapper.INSTANCE.map( source ); + + assertThat( target.getProperties() ) + .containsExactly( "first", "second" ); + + Source mapped = Issue1338Mapper.INSTANCE.map( target ); + + assertThat( mapped.getProperties() ) + .containsExactly( "first", "second" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Source.java new file mode 100644 index 0000000000..f5157b9690 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Source.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._1338; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private List properties; + + public void addProperty(String property) { + if ( properties == null ) { + properties = new ArrayList<>(); + } + properties.add( property ); + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Target.java new file mode 100644 index 0000000000..a06a52c16a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1338/Target.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.test.bugs._1338; + +import java.util.ArrayList; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private StringList properties = new StringList(); + + public void addProperty(String property) { + properties.add( property ); + } + + public void setProperties(StringList properties) { + throw new IllegalStateException( "Setter is there just as a marker it should not be used" ); + } + + public StringList getProperties() { + return properties; + } + + public static class StringList extends ArrayList { + + private StringList() { + // Constructor is private so we get a compile error if we try to instantiate it + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Callback.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Callback.java new file mode 100644 index 0000000000..f7f42079be --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Callback.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.ap.test.bugs._1339; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Context; +import org.mapstruct.MappingTarget; + +/** + * @author Filip Hrisafov + */ +public class Callback { + + @AfterMapping + public void afterMapping(@MappingTarget Issue1339Mapper.Target target, @Context int primitive) { + target.otherField = primitive; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Issue1339Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Issue1339Mapper.java new file mode 100644 index 0000000000..d3463e712b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Issue1339Mapper.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.test.bugs._1339; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = { + Callback.class +}) +public interface Issue1339Mapper { + + Issue1339Mapper INSTANCE = Mappers.getMapper( Issue1339Mapper.class ); + + class Source { + //CHECKSTYLE:OFF + public String field; + //CHECKSTYLE:ON + } + + class Target { + //CHECKSTYLE:OFF + public String field; + public int otherField; + //CHECKSTYLE:ON + } + + @Mapping(target = "otherField", ignore = true) + Target map(Source source, int primitive1, @Context int primitive2); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Issue1339Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Issue1339Test.java new file mode 100644 index 0000000000..fa1caff745 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1339/Issue1339Test.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.ap.test.bugs._1339; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue1339Mapper.class, + Callback.class +}) +@IssueKey("1339") +public class Issue1339Test { + + @ProcessorTest + public void shouldCompile() { + Issue1339Mapper.Source source = new Issue1339Mapper.Source(); + source.field = "test"; + Issue1339Mapper.Target target = Issue1339Mapper.INSTANCE.map( source, 10, 50 ); + + assertThat( target.otherField ).isEqualTo( 50 ); + assertThat( target.field ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1340/Issue1340Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1340/Issue1340Mapper.java new file mode 100644 index 0000000000..2b511956f2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1340/Issue1340Mapper.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.ap.test.bugs._1340; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1340Mapper { + + Issue1340Mapper INSTANCE = Mappers.getMapper( Issue1340Mapper.class ); + + enum Quote { + SINGLE, + MULTI + } + + enum QuoteDto { + SINGLE, + MULTI + } + + QuoteDto map(Quote quote, @Context Integer locale); + + Quote map(@Context Integer locale, QuoteDto quoteDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1340/Issue1340Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1340/Issue1340Test.java new file mode 100644 index 0000000000..3135dfff67 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1340/Issue1340Test.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.ap.test.bugs._1340; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue1340Mapper.class +}) +@IssueKey("1340") +public class Issue1340Test { + + @ProcessorTest + public void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.java new file mode 100644 index 0000000000..b2d89c96e6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Mapper.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.ap.test.bugs._1345; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface Issue1345Mapper { + + Issue1345Mapper INSTANCE = Mappers.getMapper( Issue1345Mapper.class ); + + @Mapping(target = "property", source = "readOnlyProperty") + B a2B(A a); + + @InheritInverseConfiguration(name = "a2B") + A b2A(B b); + + class A { + + private String value; + private String readOnlyProperty; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getReadOnlyProperty() { + return readOnlyProperty; + } + } + + class B { + + private String value; + private String property; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Test.java new file mode 100644 index 0000000000..7be9b0edc1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1345/Issue1345Test.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.ap.test.bugs._1345; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Sjaak Derksen + */ +@WithClasses({ + Issue1345Mapper.class +}) +@IssueKey("1345") +public class Issue1345Test { + + @ProcessorTest + public void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Issue1353Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Issue1353Mapper.java new file mode 100644 index 0000000000..e22bd946a1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Issue1353Mapper.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.ap.test.bugs._1353; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Jeffrey Smyth + */ +@Mapper +public interface Issue1353Mapper { + + Issue1353Mapper INSTANCE = Mappers.getMapper( Issue1353Mapper.class ); + + @Mappings ({ + @Mapping (target = "string2 ", source = " source.string1") + }) + Target sourceToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Issue1353Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Issue1353Test.java new file mode 100644 index 0000000000..4398d6ebcd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Issue1353Test.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.test.bugs._1353; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Jeffrey Smyth + */ +@IssueKey ("1353") +@WithClasses ({ + Issue1353Mapper.class, + Source.class, + Target.class +}) +public class Issue1353Test { + + @ProcessorTest + @ExpectedCompilationOutcome ( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic (type = Issue1353Mapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22, + message = "The property named \" source.string1\" has whitespaces," + + " using trimmed property \"source.string1\" instead." + ), + @Diagnostic (type = Issue1353Mapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22, + message = "The property named \"string2 \" has whitespaces," + + " using trimmed property \"string2\" instead." + ) + } + ) + public void shouldTrimArguments() { + Source source = new Source(); + source.setString1( "TestString" ); + + Target target = Issue1353Mapper.INSTANCE.sourceToTarget( source ); + + assertThat( target.getString2() ).isNotNull(); + assertThat( target.getString2() ).isEqualTo( source.getString1() ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Source.java new file mode 100644 index 0000000000..1a6c62db39 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Source.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.ap.test.bugs._1353; + +/** + * @author Jeffrey Smyth + */ +public class Source { + + private String string1; + + public String getString1() { + return string1; + } + + public void setString1(String string1) { + this.string1 = string1; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Target.java new file mode 100644 index 0000000000..15ece1eb3f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1353/Target.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.ap.test.bugs._1353; + +/** + * @author Jeffrey Smyth + */ +public class Target { + + private String string2; + + public String getString2() { + return string2; + } + + public void setString2(String string2) { + this.string2 = string2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Issue1359Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Issue1359Mapper.java new file mode 100644 index 0000000000..6b4e1257b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Issue1359Mapper.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.ap.test.bugs._1359; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1359Mapper { + + Issue1359Mapper INSTANCE = Mappers.getMapper( Issue1359Mapper.class ); + + void map(@MappingTarget Target target, Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Issue1359Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Issue1359Test.java new file mode 100644 index 0000000000..a412cd19f6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Issue1359Test.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.ap.test.bugs._1359; + +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.InstanceOfAssertFactories.ITERABLE; + +/** + * @author Filip Hrisafov + */ +@WithClasses( { + Issue1359Mapper.class, + Source.class, + Target.class +} ) +@IssueKey( "1359" ) +public class Issue1359Test { + + @ProcessorTest + public void shouldCompile() { + + Target target = new Target(); + assertThat( target ).extracting( "properties" ).isNull(); + + Set properties = new HashSet<>(); + properties.add( "first" ); + Source source = new Source( properties ); + Issue1359Mapper.INSTANCE.map( target, source ); + + assertThat( target ).extracting( "properties", ITERABLE ).containsExactly( "first" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Source.java new file mode 100644 index 0000000000..7646bad7c6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Source.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.ap.test.bugs._1359; + +import java.util.Set; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private Set properties; + + public Source(Set properties) { + this.properties = properties; + } + + public Set getProperties() { + return properties; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Target.java new file mode 100644 index 0000000000..9aa853ffff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1359/Target.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.ap.test.bugs._1359; + +import java.util.Set; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private Set properties; + + public void setProperties(Set properties) { + this.properties = properties; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Issue1375Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Issue1375Mapper.java new file mode 100644 index 0000000000..5fb62711db --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Issue1375Mapper.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.ap.test.bugs._1375; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1375Mapper { + + Issue1375Mapper INSTANCE = Mappers.getMapper( Issue1375Mapper.class ); + + @Mapping(target = "nested.value", source = "value") + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Issue1375Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Issue1375Test.java new file mode 100644 index 0000000000..9dcb9655f4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Issue1375Test.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.test.bugs._1375; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses( { + Target.class, + Source.class, + Issue1375Mapper.class +} ) +@IssueKey( "1375" ) +public class Issue1375Test { + + @ProcessorTest + public void shouldGenerateCorrectMapperWhenIntermediaryReadAccessorIsMissing() { + + Target target = Issue1375Mapper.INSTANCE.map( new Source( "test value" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.nested ).isNotNull(); + assertThat( target.nested.getValue() ).isEqualTo( "test value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Source.java new file mode 100644 index 0000000000..55b7de30eb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Source.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.ap.test.bugs._1375; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/Target.java new file mode 100644 index 0000000000..a7c160aaa5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1375/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.ap.test.bugs._1375; + +/** + * @author Filip Hrisafov + */ +public class Target { + + Nested nested; + + public void setNested(Nested nested) { + this.nested = nested; + } + + public static class Nested { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.java new file mode 100644 index 0000000000..4e48020c8d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Mapper.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.ap.test.bugs._1395.spring; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@Mapper(injectionStrategy = InjectionStrategy.CONSTRUCTOR, componentModel = MappingConstants.ComponentModel.SPRING, + uses = NotUsedService.class) +public interface Issue1395Mapper { + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.java new file mode 100644 index 0000000000..3417fbd707 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Issue1395Test.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.test.bugs._1395.spring; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; + +/** + * @author Filip Hrisafov + */ +@WithClasses( { + Issue1395Mapper.class, + NotUsedService.class, + Source.class, + Target.class +} ) +@WithSpring +@IssueKey( "1395" ) +public class Issue1395Test { + + @ProcessorTest + public void shouldGenerateValidCode() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java new file mode 100644 index 0000000000..ba47cf179f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.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.ap.test.bugs._1395.spring; + +/** + * @author Filip Hrisafov + */ +public interface NotUsedService { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.java new file mode 100644 index 0000000000..8f2a52d462 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Source.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.ap.test.bugs._1395.spring; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.java new file mode 100644 index 0000000000..1ba393dbe0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/Target.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.ap.test.bugs._1395.spring; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Mapper.java new file mode 100644 index 0000000000..2e500aa285 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Mapper.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.ap.test.bugs._1425; + +import java.time.LocalDate; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Bandowski + */ +@Mapper +public abstract class Issue1425Mapper { + + public static final Issue1425Mapper INSTANCE = Mappers.getMapper( Issue1425Mapper.class ); + + public abstract Target map(Source source); + + LocalDate now() { + return LocalDate.now(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Test.java new file mode 100644 index 0000000000..a71c33c396 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Test.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.test.bugs._1425; + +import org.joda.time.LocalDate; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJoda; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Bandowski + */ +@WithClasses({ + Issue1425Mapper.class, + Source.class, + Target.class +}) +@IssueKey("1425") +@WithJoda +public class Issue1425Test { + + @ProcessorTest + public void shouldTestMappingLocalDates() { + Source source = new Source(); + source.setValue( LocalDate.parse( "2018-04-18" ) ); + + Target target = Issue1425Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "2018-04-18" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Source.java new file mode 100644 index 0000000000..1a739197d8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Source.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.ap.test.bugs._1425; + +import org.joda.time.LocalDate; + +/** + * @author Christian Bandowski + */ +public class Source { + + private LocalDate value; + + public LocalDate getValue() { + return value; + } + + public void setValue(LocalDate value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Target.java new file mode 100644 index 0000000000..57d96dad49 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Target.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.ap.test.bugs._1425; + +import java.time.LocalDate; + +/** + * @author Christian Bandowski + */ +public class Target { + + private LocalDate value; + + public LocalDate getValue() { + return value; + } + + public void setValue(LocalDate value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Config.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Config.java new file mode 100644 index 0000000000..b76e6538f2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Config.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.ap.test.bugs._1435; + +import java.util.Objects; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; + +@MapperConfig(mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG, imports = Objects.class) +public interface Config { + @Mapping(expression = "java( Objects.equals( source.getName(), \"Rainbow Dash\" ) )", target = "rainbowDash") + OutObject map(InObject source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/InObject.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/InObject.java new file mode 100644 index 0000000000..27b6fa2742 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/InObject.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.ap.test.bugs._1435; + +public class InObject { + + private final String name; + + public InObject(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Issue1435Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Issue1435Mapper.java new file mode 100644 index 0000000000..c98eba2ed2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Issue1435Mapper.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.ap.test.bugs._1435; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(config = Config.class) +public interface Issue1435Mapper { + + Issue1435Mapper INSTANCE = Mappers.getMapper( Issue1435Mapper.class ); + + OutObject map(InObject source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Issue1435Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Issue1435Test.java new file mode 100644 index 0000000000..2fa173cac1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/Issue1435Test.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.test.bugs._1435; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1435") +@WithClasses({ + Config.class, + Issue1435Mapper.class, + InObject.class, + OutObject.class, +}) +public class Issue1435Test { + @ProcessorTest + public void mustNotSetListToNull() { + InObject source = new InObject( "Rainbow Dash" ); + + OutObject result = Issue1435Mapper.INSTANCE.map( source ); + + assertThat( result.isRainbowDash() ).isTrue(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/OutObject.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/OutObject.java new file mode 100644 index 0000000000..b3a16f2250 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1435/OutObject.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.ap.test.bugs._1435; + +public class OutObject { + + private boolean isRainbowDash; + + public boolean isRainbowDash() { + return isRainbowDash; + } + + public void setRainbowDash(boolean rainbowDash) { + isRainbowDash = rainbowDash; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Auction.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Auction.java new file mode 100644 index 0000000000..11f44281d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Auction.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.ap.test.bugs._1453; + +import java.util.List; +import java.util.Map; + +/** + * @author Filip Hrisafov + */ +public class Auction { + + private final List payments; + private final List otherPayments; + private Map mapPayments; + private Map mapSuperPayments; + + public Auction(List payments, List otherPayments) { + this.payments = payments; + this.otherPayments = otherPayments; + } + + public List getPayments() { + return payments; + } + + public List getOtherPayments() { + return otherPayments; + } + + public Map getMapPayments() { + return mapPayments; + } + + public void setMapPayments(Map mapPayments) { + this.mapPayments = mapPayments; + } + + public Map getMapSuperPayments() { + return mapSuperPayments; + } + + public void setMapSuperPayments(Map mapSuperPayments) { + this.mapSuperPayments = mapSuperPayments; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/AuctionDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/AuctionDto.java new file mode 100644 index 0000000000..5b8fd42a25 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/AuctionDto.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.test.bugs._1453; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Filip Hrisafov + */ +public class AuctionDto { + private List payments; + private List otherPayments; + private Map mapPayments; + private Map mapSuperPayments; + + List takePayments() { + return payments; + } + + public void setPayments(List payments) { + this.payments = payments == null ? null : new ArrayList<>( payments ); + } + + List takeOtherPayments() { + return otherPayments; + } + + public void setOtherPayments(List otherPayments) { + this.otherPayments = otherPayments; + } + + public Map getMapPayments() { + return mapPayments; + } + + public void setMapPayments(Map mapPayments) { + this.mapPayments = mapPayments == null ? null : new HashMap<>( mapPayments ); + } + + public Map getMapSuperPayments() { + return mapSuperPayments; + } + + public void setMapSuperPayments(Map mapSuperPayments) { + this.mapSuperPayments = mapSuperPayments; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Issue1453Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Issue1453Mapper.java new file mode 100644 index 0000000000..d6ba8b9917 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Issue1453Mapper.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.ap.test.bugs._1453; + +import java.util.List; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1453Mapper { + + Issue1453Mapper INSTANCE = Mappers.getMapper( Issue1453Mapper.class ); + + AuctionDto map(Auction auction); + + List mapExtend(List auctions); + + List mapSuper(List auctions); + + Map mapExtend(Map auctions); + + Map mapSuper(Map auctions); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Issue1453Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Issue1453Test.java new file mode 100644 index 0000000000..d112df0355 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Issue1453Test.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.ap.test.bugs._1453; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1453") +@WithClasses({ + Auction.class, + AuctionDto.class, + Issue1453Mapper.class, + Payment.class, + PaymentDto.class +}) +public class Issue1453Test { + + @RegisterExtension + final GeneratedSource source = new GeneratedSource().addComparisonToFixtureFor( Issue1453Mapper.class ); + + @ProcessorTest + public void shouldGenerateCorrectCode() { + + AuctionDto target = Issue1453Mapper.INSTANCE.map( new Auction( + Arrays.asList( new Payment( 100L ), new Payment( 500L ) ), + Arrays.asList( new Payment( 200L ), new Payment( 600L ) ) + ) ); + + PaymentDto first = new PaymentDto(); + first.setPrice( 100L ); + PaymentDto second = new PaymentDto(); + second.setPrice( 500L ); + + assertThat( target.takePayments() ) + .usingFieldByFieldElementComparator() + .containsExactly( + first, + second + ); + + PaymentDto firstOther = new PaymentDto(); + firstOther.setPrice( 200L ); + PaymentDto secondOther = new PaymentDto(); + secondOther.setPrice( 600L ); + + assertThat( target.takeOtherPayments() ) + .usingFieldByFieldElementComparator() + .containsExactly( + firstOther, + secondOther + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Payment.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Payment.java new file mode 100644 index 0000000000..f03461e10f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/Payment.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.ap.test.bugs._1453; + +/** + * @author Filip Hrisafov + */ +public class Payment { + + private final Long price; + + public Payment(Long price) { + this.price = price; + } + + public Long getPrice() { + return price; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/PaymentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/PaymentDto.java new file mode 100644 index 0000000000..68963c7353 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1453/PaymentDto.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.ap.test.bugs._1453; + +/** + * @author Filip Hrisafov + */ +public class PaymentDto { + + private Long price; + + public Long getPrice() { + return price; + } + + public void setPrice(Long price) { + this.price = price; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/BookMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/BookMapper.java new file mode 100644 index 0000000000..bbef1fe3a8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/BookMapper.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._1457; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public abstract class BookMapper { + + public static final BookMapper INSTANCE = Mappers.getMapper( BookMapper.class ); + + public abstract TargetBook mapBook(SourceBook sourceBook, String authorFirstName, String authorLastName); + + @AfterMapping + protected void fillAuthor(@MappingTarget TargetBook targetBook, String authorFirstName, String authorLastName) { + targetBook.setAuthorFirstName( authorFirstName ); + targetBook.setAuthorLastName( authorLastName ); + } + + @AfterMapping + protected void withoutAuthorNames(@MappingTarget TargetBook targetBook) { + targetBook.setAfterMappingWithoutAuthorName( true ); + } + + @AfterMapping + protected void withOnlyFirstName(@MappingTarget TargetBook targetBook, String authorFirstName) { + targetBook.setAfterMappingWithOnlyFirstName( authorFirstName ); + } + + @AfterMapping + protected void withOnlyLastName(@MappingTarget TargetBook targetBook, String authorLastName) { + targetBook.setAfterMappingWithOnlyLastName( authorLastName ); + } + + @AfterMapping + protected void withDifferentVariableName(@MappingTarget TargetBook targetBook, String author) { + targetBook.setAfterMappingWithDifferentVariableName( true ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/DifferentOrderingBookMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/DifferentOrderingBookMapper.java new file mode 100644 index 0000000000..c2ac556bab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/DifferentOrderingBookMapper.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.ap.test.bugs._1457; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public abstract class DifferentOrderingBookMapper { + + public static final DifferentOrderingBookMapper INSTANCE = Mappers.getMapper( DifferentOrderingBookMapper.class ); + + public abstract TargetBook mapBook(SourceBook sourceBook, String authorFirstName, String authorLastName); + + @AfterMapping + protected void fillAuthor(String authorLastName, String authorFirstName, @MappingTarget TargetBook targetBook) { + targetBook.setAuthorLastName( authorLastName ); + targetBook.setAuthorFirstName( authorFirstName ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/ErroneousBookMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/ErroneousBookMapper.java new file mode 100644 index 0000000000..a7314bbbad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/ErroneousBookMapper.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.ap.test.bugs._1457; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public abstract class ErroneousBookMapper { + + public static final ErroneousBookMapper INSTANCE = Mappers.getMapper( ErroneousBookMapper.class ); + + public abstract TargetBook mapBook(SourceBook sourceBook, String authorFirstName, String authorLastName); + + @AfterMapping + protected void fillAuthor(@MappingTarget TargetBook targetBook, String authorFirstN, String authorLastN) { + targetBook.setAuthorFirstName( authorFirstN ); + targetBook.setAuthorLastName( authorLastN ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/Issue1457Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/Issue1457Test.java new file mode 100644 index 0000000000..de1160771a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/Issue1457Test.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.ap.test.bugs._1457; + +import org.junit.jupiter.api.BeforeEach; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SourceBook.class, + TargetBook.class +}) +@IssueKey("1457") +public class Issue1457Test { + + private SourceBook sourceBook; + private String authorFirstName; + private String authorLastName; + + @BeforeEach + public void setup() { + sourceBook = new SourceBook(); + sourceBook.setIsbn( "3453146972" ); + sourceBook.setTitle( "Per Anhalter durch die Galaxis" ); + + authorFirstName = "Douglas"; + authorLastName = "Adams"; + } + + @ProcessorTest + @WithClasses({ + BookMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = @Diagnostic( + message = + "Lifecycle method has multiple matching parameters (e. g. same type), in this case please ensure to " + + "name the parameters in the lifecycle and mapping method identical. This lifecycle method will " + + "not be used for the mapping method 'org.mapstruct.ap.test.bugs._1457.TargetBook mapBook(org" + + ".mapstruct.ap.test.bugs._1457.SourceBook sourceBook, java.lang.String authorFirstName, java.lang" + + ".String authorLastName)'.", + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 43 + ) + ) + public void testMapperWithMatchingParameterNames() { + TargetBook targetBook = BookMapper.INSTANCE.mapBook( sourceBook, authorFirstName, authorLastName ); + + assertTargetBookMatchesSourceBook( targetBook ); + + assertThat( targetBook.isAfterMappingWithoutAuthorName() ).isTrue(); + assertThat( targetBook.getAfterMappingWithOnlyFirstName() ).isEqualTo( authorFirstName ); + assertThat( targetBook.getAfterMappingWithOnlyLastName() ).isEqualTo( authorLastName ); + assertThat( targetBook.isAfterMappingWithDifferentVariableName() ).isFalse(); + } + + @ProcessorTest + @WithClasses({ + DifferentOrderingBookMapper.class + }) + public void testMapperWithMatchingParameterNamesAndDifferentOrdering() { + TargetBook targetBook = DifferentOrderingBookMapper.INSTANCE.mapBook( + sourceBook, + authorFirstName, + authorLastName + ); + + assertTargetBookMatchesSourceBook( targetBook ); + } + + @ProcessorTest + @WithClasses({ + ObjectFactoryBookMapper.class + }) + public void testMapperWithObjectFactory() { + TargetBook targetBook = ObjectFactoryBookMapper.INSTANCE.mapBook( + sourceBook, + authorFirstName, + authorLastName + ); + + assertTargetBookMatchesSourceBook( targetBook ); + } + + private void assertTargetBookMatchesSourceBook(TargetBook targetBook) { + assertThat( sourceBook.getIsbn() ).isEqualTo( targetBook.getIsbn() ); + assertThat( sourceBook.getTitle() ).isEqualTo( targetBook.getTitle() ); + assertThat( authorFirstName ).isEqualTo( targetBook.getAuthorFirstName() ); + assertThat( authorLastName ).isEqualTo( targetBook.getAuthorLastName() ); + } + + @ProcessorTest + @WithClasses({ + ErroneousBookMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = @Diagnostic( + message = + "Lifecycle method has multiple matching parameters (e. g. same type), in this case please ensure to " + + "name the parameters in the lifecycle and mapping method identical. This lifecycle method will " + + "not be used for the mapping method 'org.mapstruct.ap.test.bugs._1457.TargetBook mapBook(org" + + ".mapstruct.ap.test.bugs._1457.SourceBook sourceBook, java.lang.String authorFirstName, java.lang" + + ".String authorLastName)'.", + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22 + ) + ) + public void testMapperWithoutMatchingParameterNames() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/ObjectFactoryBookMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/ObjectFactoryBookMapper.java new file mode 100644 index 0000000000..038b44653c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/ObjectFactoryBookMapper.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.ap.test.bugs._1457; + +import org.mapstruct.Mapper; +import org.mapstruct.ObjectFactory; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public abstract class ObjectFactoryBookMapper { + + public static final ObjectFactoryBookMapper INSTANCE = Mappers.getMapper( ObjectFactoryBookMapper.class ); + + public abstract TargetBook mapBook(SourceBook sourceBook, String authorFirstName, String authorLastName); + + @ObjectFactory + protected TargetBook createTargetBook(String authorFirstName, String authorLastName) { + TargetBook targetBook = new TargetBook(); + targetBook.setAuthorFirstName( authorFirstName ); + targetBook.setAuthorLastName( authorLastName ); + + return targetBook; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/SourceBook.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/SourceBook.java new file mode 100644 index 0000000000..650e51fe4c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/SourceBook.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.ap.test.bugs._1457; + +public class SourceBook { + private String isbn; + private String title; + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/TargetBook.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/TargetBook.java new file mode 100644 index 0000000000..1698c537f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1457/TargetBook.java @@ -0,0 +1,83 @@ +/* + * 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.test.bugs._1457; + +public class TargetBook { + + private String isbn; + private String title; + private String authorFirstName; + private String authorLastName; + + private boolean afterMappingWithoutAuthorName; + private String afterMappingWithOnlyFirstName; + private String afterMappingWithOnlyLastName; + private boolean afterMappingWithDifferentVariableName; + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getAuthorFirstName() { + return authorFirstName; + } + + public void setAuthorFirstName(String authorFirstName) { + this.authorFirstName = authorFirstName; + } + + public String getAuthorLastName() { + return authorLastName; + } + + public void setAuthorLastName(String authorLastName) { + this.authorLastName = authorLastName; + } + + public boolean isAfterMappingWithoutAuthorName() { + return afterMappingWithoutAuthorName; + } + + public void setAfterMappingWithoutAuthorName(boolean afterMappingWithoutAuthorName) { + this.afterMappingWithoutAuthorName = afterMappingWithoutAuthorName; + } + + public String getAfterMappingWithOnlyFirstName() { + return afterMappingWithOnlyFirstName; + } + + public void setAfterMappingWithOnlyFirstName(String afterMappingWithOnlyFirstName) { + this.afterMappingWithOnlyFirstName = afterMappingWithOnlyFirstName; + } + + public String getAfterMappingWithOnlyLastName() { + return afterMappingWithOnlyLastName; + } + + public void setAfterMappingWithOnlyLastName(String afterMappingWithOnlyLastName) { + this.afterMappingWithOnlyLastName = afterMappingWithOnlyLastName; + } + + public boolean isAfterMappingWithDifferentVariableName() { + return afterMappingWithDifferentVariableName; + } + + public void setAfterMappingWithDifferentVariableName(boolean afterMappingWithDifferentVariableName) { + this.afterMappingWithDifferentVariableName = afterMappingWithDifferentVariableName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java new file mode 100644 index 0000000000..1d0d4fccbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.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.test.bugs._1460; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Bandowski + */ +@Mapper +public abstract class Issue1460Mapper { + + public static final Issue1460Mapper INSTANCE = Mappers.getMapper( Issue1460Mapper.class ); + + public abstract Target map(Source source); + + public abstract Value forceUsageOfIssue1460Enum(Issue1460Enum source); + + public abstract Value forceUsageOfLocale(Locale source); + + public abstract Value forceUsageOfLocalDate(LocalDate source); + + public abstract Value forceUsageOfDateTime(DateTime source); + + public static class Issue1460Enum { + } + + public static class Locale { + } + + public static class LocalDate { + } + + public static class DateTime { + } + + public static class Value { + + private final T source; + + public Value(T source) { + this.source = source; + } + + public T getSource() { + return source; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Test.java new file mode 100644 index 0000000000..e9c97f39bc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Test.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.test.bugs._1460; + +import java.util.Date; +import java.util.Locale; + +import org.joda.time.DateTime; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJoda; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Bandowski + */ +@WithClasses({ + Issue1460Mapper.class, + Source.class, + Target.class +}) +@IssueKey("1460") +@WithJoda +public class Issue1460Test { + + @ProcessorTest + public void shouldTestMappingLocalDates() { + long dateInMs = 1524693600000L; + String dateAsString = "2018-04-26"; + String dateTimeAsString = dateAsString + "T00:00:00+00:00"; + + Source source = new Source(); + source.setStringToEnum( "OK" ); + source.setDateToJodaDateTime( new Date( dateInMs ) ); + source.setJodaDateTimeToCalendar( DateTime.parse( dateTimeAsString ) ); + + Target target = Issue1460Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getStringToEnum() ).isEqualTo( Target.Issue1460Enum.OK ); + assertThat( target.getDateToJodaDateTime() ).isEqualTo( + new DateTime( new Date( dateInMs ) ) + ); + assertThat( target.getJodaDateTimeToCalendar().getTimeInMillis() ).isEqualTo( + DateTime.parse( dateTimeAsString ).toCalendar( Locale.getDefault() ).getTimeInMillis() + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Source.java new file mode 100644 index 0000000000..c97909aeec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Source.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._1460; + +import java.util.Date; + +import org.joda.time.DateTime; + +public class Source { + private String stringToEnum; + private DateTime jodaDateTimeToCalendar; + private Date dateToJodaDateTime; + + public String getStringToEnum() { + return stringToEnum; + } + + public void setStringToEnum(String stringToEnum) { + this.stringToEnum = stringToEnum; + } + + public DateTime getJodaDateTimeToCalendar() { + return jodaDateTimeToCalendar; + } + + public void setJodaDateTimeToCalendar(DateTime jodaDateTimeToCalendar) { + this.jodaDateTimeToCalendar = jodaDateTimeToCalendar; + } + + public Date getDateToJodaDateTime() { + return dateToJodaDateTime; + } + + public void setDateToJodaDateTime(Date dateToJodaDateTime) { + this.dateToJodaDateTime = dateToJodaDateTime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Target.java new file mode 100644 index 0000000000..e680f7d29c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Target.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.ap.test.bugs._1460; + +import java.util.Calendar; + +import org.joda.time.DateTime; + +public class Target { + public enum Issue1460Enum { + OK, KO; + } + + private Issue1460Enum stringToEnum; + private Calendar jodaDateTimeToCalendar; + private DateTime dateToJodaDateTime; + + public Issue1460Enum getStringToEnum() { + return stringToEnum; + } + + public void setStringToEnum(Issue1460Enum stringToEnum) { + this.stringToEnum = stringToEnum; + } + + public Calendar getJodaDateTimeToCalendar() { + return jodaDateTimeToCalendar; + } + + public void setJodaDateTimeToCalendar(Calendar jodaDateTimeToCalendar) { + this.jodaDateTimeToCalendar = jodaDateTimeToCalendar; + } + + public DateTime getDateToJodaDateTime() { + return dateToJodaDateTime; + } + + public void setDateToJodaDateTime(DateTime dateToJodaDateTime) { + this.dateToJodaDateTime = dateToJodaDateTime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.java new file mode 100644 index 0000000000..b9bedfe451 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeMapper.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.test.bugs._1460.java8; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Bandowski + */ +@Mapper +public abstract class Issue1460JavaTimeMapper { + + public static final Issue1460JavaTimeMapper INSTANCE = Mappers.getMapper( Issue1460JavaTimeMapper.class ); + + public abstract Target map(Source source); + + public abstract LocalTarget forceUsageOfLocalDate(LocalDate source); + + public static class LocalDate { + } + + public static class LocalTarget { + + private final LocalDate source; + + public LocalTarget(LocalDate source) { + this.source = source; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeTest.java new file mode 100644 index 0000000000..829402efaa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Issue1460JavaTimeTest.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.ap.test.bugs._1460.java8; + +import java.time.LocalDate; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Bandowski + */ +@WithClasses({ + Issue1460JavaTimeMapper.class, + Source.class, + Target.class +}) +@IssueKey("1460") +public class Issue1460JavaTimeTest { + + @ProcessorTest + public void shouldTestMappingLocalDates() { + String dateAsString = "2018-04-26"; + + Source source = new Source(); + source.setStringToJavaLocalDate( dateAsString ); + + Target target = Issue1460JavaTimeMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getStringToJavaLocalDate() ).isEqualTo( LocalDate.parse( dateAsString ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Source.java new file mode 100644 index 0000000000..76b5124d2e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Source.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.ap.test.bugs._1460.java8; + +public class Source { + private String stringToJavaLocalDate; + + public String getStringToJavaLocalDate() { + return stringToJavaLocalDate; + } + + public void setStringToJavaLocalDate(String stringToJavaLocalDate) { + this.stringToJavaLocalDate = stringToJavaLocalDate; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Target.java new file mode 100644 index 0000000000..1a7554b54f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/java8/Target.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.ap.test.bugs._1460.java8; + +import java.time.LocalDate; + +public class Target { + private LocalDate stringToJavaLocalDate; + + public LocalDate getStringToJavaLocalDate() { + return stringToJavaLocalDate; + } + + public void setStringToJavaLocalDate(LocalDate stringToJavaLocalDate) { + this.stringToJavaLocalDate = stringToJavaLocalDate; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/BigDecimalWrapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/BigDecimalWrapper.java new file mode 100644 index 0000000000..d62464c11a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/BigDecimalWrapper.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.ap.test.bugs._1482; + +import java.math.BigDecimal; + +public class BigDecimalWrapper implements ValueWrapper { + + private final BigDecimal value; + + public BigDecimalWrapper(BigDecimal value) { + this.value = value; + } + + @Override + public BigDecimal getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Issue1482Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Issue1482Test.java new file mode 100644 index 0000000000..d59b58dbda --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Issue1482Test.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.ap.test.bugs._1482; + +import java.math.BigDecimal; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Source.class, + Source2.class, + Target.class, + SourceEnum.class, + BigDecimalWrapper.class, + ValueWrapper.class +}) +@IssueKey(value = "1482") +public class Issue1482Test { + + @ProcessorTest + @WithClasses( SourceTargetMapper.class ) + public void testForward() { + + Source source = new Source(); + source.setTest( SourceEnum.VAL1 ); + source.setWrapper( new BigDecimalWrapper( new BigDecimal( 5 ) ) ); + + Target target = SourceTargetMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getTest() ).isEqualTo( "value1" ); + assertThat( target.getBigDecimal() ).isEqualTo( new BigDecimal( 5 ) ); + + } + + @ProcessorTest + @WithClasses( TargetSourceMapper.class ) + public void testReverse() { + + Target target = new Target(); + target.setBigDecimal( new BigDecimal( 5 ) ); + target.setTest( "VAL1" ); + + Source2 source2 = TargetSourceMapper.INSTANCE.map( target ); + + assertThat( source2 ).isNotNull(); + assertThat( source2.getTest() ).isEqualTo( SourceEnum.VAL1 ); + assertThat( source2.getWrapper().getValue() ).isEqualTo( new BigDecimal( 5 ) ); + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Source.java new file mode 100644 index 0000000000..78c98c8af7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Source.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.ap.test.bugs._1482; + +public class Source { + + private SourceEnum test; + private BigDecimalWrapper wrapper; + + public SourceEnum getTest() { + return test; + } + + public void setTest(SourceEnum test) { + this.test = test; + } + + public BigDecimalWrapper getWrapper() { + return wrapper; + } + + public void setWrapper(BigDecimalWrapper wrapper) { + this.wrapper = wrapper; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Source2.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Source2.java new file mode 100644 index 0000000000..3ef01c76bb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Source2.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.test.bugs._1482; + +import java.math.BigDecimal; + +public class Source2 { + + private Enum test; + private ValueWrapper wrapper; + + public Enum getTest() { + return test; + } + + public void setTest(Enum test) { + this.test = test; + } + + public ValueWrapper getWrapper() { + return wrapper; + } + + public void setWrapper(ValueWrapper wrapper) { + this.wrapper = wrapper; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/SourceEnum.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/SourceEnum.java new file mode 100644 index 0000000000..d421b17ef1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/SourceEnum.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.ap.test.bugs._1482; + +public enum SourceEnum { + + VAL1( "value1" ), + VAL2( "value2" ); + + private final String val; + + SourceEnum(String val) { + this.val = val; + } + + public String toString() { + return val; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/SourceTargetMapper.java new file mode 100644 index 0000000000..01a50311ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/SourceTargetMapper.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.ap.test.bugs._1482; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class SourceTargetMapper { + + public static final SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping( target = "bigDecimal", source = "wrapper" ) + abstract Target map(Source source); + + protected String map(Enum e) { + return e.toString(); + } + + protected T map(ValueWrapper in) { + return in.getValue(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Target.java new file mode 100644 index 0000000000..3c3ede8016 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Target.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.test.bugs._1482; + +import java.math.BigDecimal; + +public class Target { + + private String test; + private BigDecimal bigDecimal; + + public String getTest() { + return test; + } + + public void setTest(String test) { + this.test = test; + } + + public BigDecimal getBigDecimal() { + return bigDecimal; + } + + public void setBigDecimal(BigDecimal bigDecimal) { + this.bigDecimal = bigDecimal; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/TargetSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/TargetSourceMapper.java new file mode 100644 index 0000000000..2ac081f611 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/TargetSourceMapper.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.test.bugs._1482; + +import java.math.BigDecimal; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class TargetSourceMapper { + + public static final TargetSourceMapper INSTANCE = Mappers.getMapper( TargetSourceMapper.class ); + + @Mapping(target = "wrapper", source = "bigDecimal") + abstract Source2 map(Target target); + + protected > T map(String in, @TargetType Classclz ) { + if ( clz.isAssignableFrom( SourceEnum.class ) ) { + return (T) SourceEnum.valueOf( in ); + } + return null; + } + + protected ValueWrapper map(T in) { + if ( in instanceof BigDecimal ) { + return (ValueWrapper) new BigDecimalWrapper( (BigDecimal) in ); + + } + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/ValueWrapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/ValueWrapper.java new file mode 100644 index 0000000000..09f331c5ef --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/ValueWrapper.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.ap.test.bugs._1482; + +public interface ValueWrapper { + T getValue(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Issue1523Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Issue1523Mapper.java new file mode 100644 index 0000000000..044e714e65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Issue1523Mapper.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.ap.test.bugs._1523.java8; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Bandowski + */ +@Mapper +public abstract class Issue1523Mapper { + + public static final Issue1523Mapper INSTANCE = Mappers.getMapper( Issue1523Mapper.class ); + + public abstract Target map(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Issue1523Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Issue1523Test.java new file mode 100644 index 0000000000..825e318356 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Issue1523Test.java @@ -0,0 +1,57 @@ +/* + * 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.test.bugs._1523.java8; + +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.TimeZone; + +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This test will evaluate if the conversion from {@code Calendar} to {@code XMLGregorianCalendar} works in case the + * default timezone was not used. Additionally a direct conversion between {@code ZonedDateTime} to + * {@code XMLGregorianCalendar} was added for this issue to improve readability / performance. This will be tested as + * well. + * + * @author Christian Bandowski + */ +@WithClasses({ + Issue1523Mapper.class, + Source.class, + Target.class +}) +@IssueKey("1523") +// we want to test that the timezone will correctly be used in mapped XMLGregorianCalendar and not the +// default one, so we must ensure that we use a different timezone than the default one -> set the default +// one explicitly to UTC +@DefaultTimeZone("UTC") +public class Issue1523Test { + + @ProcessorTest + public void testThatCorrectTimeZoneWillBeUsedInTarget() { + Source source = new Source(); + // default one was explicitly set to UTC, thus +01:00 is a different one + source.setValue( ZonedDateTime.parse( "2018-06-15T00:00:00+01:00" ) ); + Calendar cal = Calendar.getInstance( TimeZone.getTimeZone( "GMT+01:00" ) ); + cal.set( 2018, Calendar.MARCH, 15, 00, 00, 00 ); + source.setValue2( cal ); + + Target target = Issue1523Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNotNull(); + assertThat( target.getValue2() ).isNotNull(); + // +01:00 -> offset is 60 min + assertThat( target.getValue().getTimezone() ).isEqualTo( 60 ); + assertThat( target.getValue2().getTimezone() ).isEqualTo( 60 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Source.java new file mode 100644 index 0000000000..1075599c54 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Source.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.test.bugs._1523.java8; + +import java.time.ZonedDateTime; +import java.util.Calendar; + +public class Source { + private ZonedDateTime value; + private Calendar value2; + + public ZonedDateTime getValue() { + return value; + } + + public void setValue(ZonedDateTime value) { + this.value = value; + } + + public Calendar getValue2() { + return value2; + } + + public void setValue2(Calendar value2) { + this.value2 = value2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Target.java new file mode 100644 index 0000000000..7492d58b99 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1523/java8/Target.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.ap.test.bugs._1523.java8; + +import javax.xml.datatype.XMLGregorianCalendar; + +public class Target { + private XMLGregorianCalendar value; + private XMLGregorianCalendar value2; + + public XMLGregorianCalendar getValue() { + return value; + } + + public void setValue(XMLGregorianCalendar value) { + this.value = value; + } + + public XMLGregorianCalendar getValue2() { + return value2; + } + + public void setValue2(XMLGregorianCalendar value2) { + this.value2 = value2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Mapper.java new file mode 100644 index 0000000000..caafee8d3f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Mapper.java @@ -0,0 +1,85 @@ +/* + * 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.test.bugs._1541; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeanMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Bandowski + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) // IGNORE to keep this test-mapper small and clean +public abstract class Issue1541Mapper { + + public static final Issue1541Mapper INSTANCE = Mappers.getMapper( Issue1541Mapper.class ); + + public abstract Target mapWithVarArgs(String code, String... parameters); + + public abstract Target mapWithArray(String code, String[] parameters); + + @Mapping(target = "parameters2", source = "parameters") + public abstract Target mapWithReassigningVarArgs(String code, String... parameters); + + public abstract Target mapWithArrayAndVarArgs(String code, String[] parameters, String... parameters2); + + @Mappings({ + @Mapping(target = "parameters", ignore = true) + }) + @BeanMapping(qualifiedByName = "afterMappingParametersAsArray") + public abstract Target mapParametersAsArrayInAfterMapping(String code, String... parameters); + + @AfterMapping + @Named( "afterMappingParametersAsArray" ) + protected void afterMappingParametersAsArray(@MappingTarget Target target, String[] parameters) { + target.setAfterMappingWithArrayCalled( true ); + target.setParameters( parameters ); + } + + @Mappings({ + @Mapping(target = "parameters", ignore = true) + }) + @BeanMapping(qualifiedByName = "afterMappingParametersAsVarArgs") + public abstract Target mapParametersAsVarArgsInAfterMapping(String code, String... parameters); + + @AfterMapping + @Named( "afterMappingParametersAsVarArgs" ) + protected void afterMappingParametersAsVarArgs(@MappingTarget Target target, String... parameters) { + target.setAfterMappingWithVarArgsCalled( true ); + target.setParameters( parameters ); + } + + @Mapping(target = "parameters2", ignore = true) + @BeanMapping(qualifiedByName = "afterMappingContextAsVarArgsUsingVarArgs") + public abstract Target mapContextWithVarArgsInAfterMappingWithVarArgs(String code, String[] parameters, + @Context String... context); + + @AfterMapping + @Named( "afterMappingContextAsVarArgsUsingVarArgs" ) + protected void afterMappingContextAsVarArgsUsingVarArgs(@MappingTarget Target target, @Context String... context) { + target.setAfterMappingContextWithVarArgsAsVarArgsCalled( true ); + target.setParameters2( context ); + } + + @Mapping(target = "parameters2", ignore = true) + @BeanMapping(qualifiedByName = "afterMappingContextAsVarArgsUsingArray") + public abstract Target mapContextWithVarArgsInAfterMappingWithArray(String code, String[] parameters, + @Context String... context); + + @AfterMapping + @Named( "afterMappingContextAsVarArgsUsingArray" ) + protected void afterMappingContextAsVarArgsUsingArray(@MappingTarget Target target, @Context String[] context) { + target.setAfterMappingContextWithVarArgsAsArrayCalled( true ); + target.setParameters2( context ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Test.java new file mode 100644 index 0000000000..01f7ed92a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Issue1541Test.java @@ -0,0 +1,145 @@ +/* + * 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.test.bugs._1541; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Christian Bandowski + */ +@WithClasses({ + Issue1541Mapper.class, + Target.class +}) +@IssueKey("1541") +public class Issue1541Test { + + @ProcessorTest + public void testMappingWithVarArgs() { + Target target = Issue1541Mapper.INSTANCE.mapWithVarArgs( "code", "1", "2" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @ProcessorTest + public void testMappingWithArray() { + Target target = Issue1541Mapper.INSTANCE.mapWithArray( "code", new String[] { "1", "2" } ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).isNull(); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @ProcessorTest + public void testMappingWithVarArgsReassignment() { + Target target = Issue1541Mapper.INSTANCE.mapWithReassigningVarArgs( "code", "1", "2" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).isNull(); + assertThat( target.getParameters2() ).contains( "1", "2" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @ProcessorTest + public void testMappingWithArrayAndVarArgs() { + Target target = Issue1541Mapper.INSTANCE.mapWithArrayAndVarArgs( "code", new String[] { "1", "2" }, "3", "4" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).contains( "3", "4" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @ProcessorTest + public void testVarArgsInAfterMappingAsArray() { + Target target = Issue1541Mapper.INSTANCE.mapParametersAsArrayInAfterMapping( "code", "1", "2" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).isNull(); + assertThat( target.isAfterMappingWithArrayCalled() ).isTrue(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @ProcessorTest + public void testVarArgsInAfterMappingAsVarArgs() { + Target target = Issue1541Mapper.INSTANCE.mapParametersAsVarArgsInAfterMapping( "code", "1", "2" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).isNull(); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isTrue(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @ProcessorTest + public void testVarArgsInContextWithVarArgsAfterMapping() { + Target target = Issue1541Mapper.INSTANCE.mapContextWithVarArgsInAfterMappingWithVarArgs( + "code", + new String[] { "1", "2" }, + "3", + "4" + ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).contains( "3", "4" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isTrue(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); + } + + @ProcessorTest + public void testVarArgsInContextWithArrayAfterMapping() { + Target target = Issue1541Mapper.INSTANCE.mapContextWithVarArgsInAfterMappingWithArray( + "code", + new String[] { "1", "2" }, + "3", + "4" + ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "code" ); + assertThat( target.getParameters() ).contains( "1", "2" ); + assertThat( target.getParameters2() ).contains( "3", "4" ); + assertThat( target.isAfterMappingWithArrayCalled() ).isFalse(); + assertThat( target.isAfterMappingWithVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsVarArgsCalled() ).isFalse(); + assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isTrue(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Target.java new file mode 100644 index 0000000000..b57c7a476f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1541/Target.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.ap.test.bugs._1541; + +public class Target { + private String code; + private String[] parameters; + private String[] parameters2; + + private boolean afterMappingWithVarArgsCalled = false; + private boolean afterMappingWithArrayCalled = false; + private boolean afterMappingContextWithVarArgsAsVarArgsCalled = false; + private boolean afterMappingContextWithVarArgsAsArrayCalled = false; + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String[] getParameters() { + return parameters; + } + + public void setParameters(String[] parameters) { + this.parameters = parameters; + } + + public String[] getParameters2() { + return parameters2; + } + + public void setParameters2(String[] parameters2) { + this.parameters2 = parameters2; + } + + public boolean isAfterMappingWithVarArgsCalled() { + return afterMappingWithVarArgsCalled; + } + + public void setAfterMappingWithVarArgsCalled(boolean afterMappingWithVarArgsCalled) { + this.afterMappingWithVarArgsCalled = afterMappingWithVarArgsCalled; + } + + public boolean isAfterMappingWithArrayCalled() { + return afterMappingWithArrayCalled; + } + + public void setAfterMappingWithArrayCalled(boolean afterMappingWithArrayCalled) { + this.afterMappingWithArrayCalled = afterMappingWithArrayCalled; + } + + public boolean isAfterMappingContextWithVarArgsAsVarArgsCalled() { + return afterMappingContextWithVarArgsAsVarArgsCalled; + } + + public void setAfterMappingContextWithVarArgsAsVarArgsCalled( + boolean afterMappingContextWithVarArgsAsVarArgsCalled) { + this.afterMappingContextWithVarArgsAsVarArgsCalled = afterMappingContextWithVarArgsAsVarArgsCalled; + } + + public boolean isAfterMappingContextWithVarArgsAsArrayCalled() { + return afterMappingContextWithVarArgsAsArrayCalled; + } + + public void setAfterMappingContextWithVarArgsAsArrayCalled(boolean afterMappingContextWithVarArgsAsArrayCalled) { + this.afterMappingContextWithVarArgsAsArrayCalled = afterMappingContextWithVarArgsAsArrayCalled; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Issue1552Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Issue1552Mapper.java new file mode 100644 index 0000000000..ca97c0f0b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Issue1552Mapper.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._1552; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1552Mapper { + + Issue1552Mapper INSTANCE = Mappers.getMapper( Issue1552Mapper.class ); + + @Mappings({ + @Mapping(target = "first.value", constant = "constant"), + @Mapping(target = "second.value", source = "sourceTwo") + }) + Target twoArgsWithConstant(String sourceOne, String sourceTwo); + + @Mappings({ + @Mapping(target = "first.value", expression = "java(\"expression\")"), + @Mapping(target = "second.value", source = "sourceTwo") + }) + Target twoArgsWithExpression(String sourceOne, String sourceTwo); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Issue1552Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Issue1552Test.java new file mode 100644 index 0000000000..2dcaf182c6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Issue1552Test.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.ap.test.bugs._1552; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue1552Mapper.class, + Target.class +}) +@IssueKey("1552") +public class Issue1552Test { + + @ProcessorTest + public void shouldCompile() { + Target target = Issue1552Mapper.INSTANCE.twoArgsWithConstant( "first", "second" ); + + assertThat( target ).isNotNull(); + assertThat( target.getFirst() ).isNotNull(); + assertThat( target.getFirst().getValue() ).isEqualTo( "constant" ); + assertThat( target.getSecond() ).isNotNull(); + assertThat( target.getSecond().getValue() ).isEqualTo( "second" ); + + + target = Issue1552Mapper.INSTANCE.twoArgsWithExpression( "third", "fourth" ); + + assertThat( target ).isNotNull(); + assertThat( target.getFirst() ).isNotNull(); + assertThat( target.getFirst().getValue() ).isEqualTo( "expression" ); + assertThat( target.getSecond() ).isNotNull(); + assertThat( target.getSecond().getValue() ).isEqualTo( "fourth" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Target.java new file mode 100644 index 0000000000..93c80ebd1e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1552/Target.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.ap.test.bugs._1552; + +/** + * @author Filip Hrisafov + */ +public class Target { + private Inner first; + private Inner second; + + public Inner getFirst() { + return first; + } + + public void setFirst(Inner first) { + this.first = first; + } + + public Inner getSecond() { + return second; + } + + public void setSecond(Inner second) { + this.second = second; + } + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Car.java new file mode 100755 index 0000000000..d18c49246a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Car.java @@ -0,0 +1,81 @@ +/* + * 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.test.bugs._1558.java8; + +public class Car { + private boolean[] booleanData; + private byte[] data; + private short[] shortData; + private int[] intData; + private long[] longData; + private char[] charData; + private float[] floatData; + private double[] doubleData; + + public boolean[] getBooleanData() { + return booleanData; + } + + public void setBooleanData(boolean[] booleanData) { + this.booleanData = booleanData; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + public short[] getShortData() { + return shortData; + } + + public void setShortData(short[] shortData) { + this.shortData = shortData; + } + + public int[] getIntData() { + return intData; + } + + public void setIntData(int[] intData) { + this.intData = intData; + } + + public long[] getLongData() { + return longData; + } + + public void setLongData(long[] longData) { + this.longData = longData; + } + + public char[] getCharData() { + return charData; + } + + public void setCharData(char[] charData) { + this.charData = charData; + } + + public float[] getFloatData() { + return floatData; + } + + public void setFloatData(float[] floatData) { + this.floatData = floatData; + } + + public double[] getDoubleData() { + return doubleData; + } + + public void setDoubleData(double[] doubleData) { + this.doubleData = doubleData; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Car2.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Car2.java new file mode 100755 index 0000000000..337c5dd774 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Car2.java @@ -0,0 +1,89 @@ +/* + * 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.test.bugs._1558.java8; + +public class Car2 { + private boolean[] booleanData; + private byte[] data; + private short[] shortData; + private int[] intData; + private long[] longData; + private char[] charData; + private float[] floatData; + private double[] doubleData; + + @NotNull + public boolean[] getBooleanData() { + return booleanData; + } + + public void setBooleanData(boolean[] booleanData) { + this.booleanData = booleanData; + } + + @NotNull + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + @NotNull + public short[] getShortData() { + return shortData; + } + + public void setShortData(short[] shortData) { + this.shortData = shortData; + } + + @NotNull + public int[] getIntData() { + return intData; + } + + public void setIntData(int[] intData) { + this.intData = intData; + } + + @NotNull + public long[] getLongData() { + return longData; + } + + public void setLongData(long[] longData) { + this.longData = longData; + } + + @NotNull + public char[] getCharData() { + return charData; + } + + public void setCharData(char[] charData) { + this.charData = charData; + } + + @NotNull + public float[] getFloatData() { + return floatData; + } + + public void setFloatData(float[] floatData) { + this.floatData = floatData; + } + + @NotNull + public double[] getDoubleData() { + return doubleData; + } + + public void setDoubleData(double[] doubleData) { + this.doubleData = doubleData; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/CarMapper.java new file mode 100755 index 0000000000..2bd71d0c62 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/CarMapper.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.ap.test.bugs._1558.java8; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + Car toCar(Car2 car2); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Issue1558Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Issue1558Test.java new file mode 100644 index 0000000000..38bafc1101 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/Issue1558Test.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.ap.test.bugs._1558.java8; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Sjaak Derksen + */ +@WithClasses({ + NotNull.class, + CarMapper.class, + Car.class, + Car2.class +}) +@IssueKey("1558") +public class Issue1558Test { + + @ProcessorTest + public void testShouldCompile() { + Car2 car = new Car2(); + Car target = CarMapper.INSTANCE.toCar( car ); + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/NotNull.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/NotNull.java new file mode 100644 index 0000000000..cf059a7c14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1558/java8/NotNull.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.ap.test.bugs._1558.java8; + +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; + +@Target({ + ElementType.METHOD, + ElementType.TYPE_USE +}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NotNull { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Issue1561Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Issue1561Mapper.java new file mode 100644 index 0000000000..95065f5dfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Issue1561Mapper.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.ap.test.bugs._1561; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Sebastian Haberey + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue1561Mapper { + + Issue1561Mapper + INSTANCE = Mappers.getMapper( Issue1561Mapper.class ); + + @Mapping( target = "nestedTarget.properties", source = "properties") + Target map(Source source); + + @InheritInverseConfiguration + Source map(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Issue1561Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Issue1561Test.java new file mode 100644 index 0000000000..3e17e1c605 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Issue1561Test.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._1561; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sebastian Haberey + */ +@IssueKey("1561") +@WithClasses({ + Issue1561Mapper.class, + Source.class, + Target.class, + NestedTarget.class +}) +public class Issue1561Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + Issue1561Mapper.class + ); + + @ProcessorTest + public void shouldCorrectlyUseAdder() { + + Source source = new Source(); + source.addProperty( "first" ); + source.addProperty( "second" ); + + Target target = Issue1561Mapper.INSTANCE.map( source ); + + assertThat( target.getNestedTarget().getProperties() ).containsExactly( "first", "second" ); + + Source mapped = Issue1561Mapper.INSTANCE.map( target ); + + assertThat( mapped.getProperties() ).containsExactly( "first", "second" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/NestedTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/NestedTarget.java new file mode 100644 index 0000000000..479d91f0e6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/NestedTarget.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.ap.test.bugs._1561; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +/** + * @author Sebastian Haberey + */ +public class NestedTarget { + + private List properties = new ArrayList<>(); + + public Stream getProperties() { + return properties.stream(); + } + + public void addProperty(String property) { + properties.add( property ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Source.java new file mode 100644 index 0000000000..5afe78f12c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Source.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.ap.test.bugs._1561; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Sebastian Haberey + */ +public class Source { + + private List properties = new ArrayList<>(); + + public List getProperties() { + return properties; + } + + public void addProperty(String property) { + properties.add( property ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Target.java new file mode 100644 index 0000000000..29c4f28b0c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/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.ap.test.bugs._1561; + +public class Target { + + private NestedTarget nestedTarget; + + public NestedTarget getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(NestedTarget nestedTarget) { + this.nestedTarget = nestedTarget; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/AbstractBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/AbstractBuilder.java new file mode 100644 index 0000000000..d97fccf9be --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/AbstractBuilder.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.ap.test.bugs._1566; + +/** + * @author Filip Hrisafov + */ +public abstract class AbstractBuilder> { + String id; + + @SuppressWarnings("unchecked") + public T id(final String id) { + this.id = id; + return (T) this; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Mapper.java new file mode 100644 index 0000000000..2ed6ed6d75 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Mapper.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.ap.test.bugs._1566; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1566Mapper { + + Issue1566Mapper INSTANCE = Mappers.getMapper( Issue1566Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Test.java new file mode 100644 index 0000000000..5de2af6ab3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Issue1566Test.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.test.bugs._1566; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + AbstractBuilder.class, + Issue1566Mapper.class, + Source.class, + Target.class +}) +@IssueKey("1566") +public class Issue1566Test { + + @ProcessorTest + public void genericMapperIsCorrectlyUsed() { + Source source = new Source(); + source.setId( "id-123" ); + source.setName( "Source" ); + + Target target = Issue1566Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "id-123" ); + assertThat( target.getName() ).isEqualTo( "Source" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Source.java new file mode 100644 index 0000000000..73f3820d00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Source.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.ap.test.bugs._1566; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String id; + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Target.java new file mode 100644 index 0000000000..64a336af3c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1566/Target.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.ap.test.bugs._1566; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String id; + private final String name; + + private Target(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends AbstractBuilder { + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Target build() { + return new Target( id, name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Issue1569Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Issue1569Mapper.java new file mode 100644 index 0000000000..b97d0f75fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Issue1569Mapper.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.ap.test.bugs._1569.java8; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1569Mapper { + + Issue1569Mapper INSTANCE = Mappers.getMapper( Issue1569Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Issue1569Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Issue1569Test.java new file mode 100644 index 0000000000..1bc7f4ca8d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Issue1569Test.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._1569.java8; + +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneOffset; +import java.util.Date; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue1569Mapper.class, + Source.class, + Target.class +}) +@IssueKey("1569") +public class Issue1569Test { + + @ProcessorTest + public void shouldGenerateCorrectMapping() { + Source source = new Source(); + Date date = Date.from( LocalDate.of( 2018, Month.AUGUST, 5 ).atTime( 20, 30 ).toInstant( ZoneOffset.UTC ) ); + source.setPromotionDate( date ); + + Target target = Issue1569Mapper.INSTANCE.map( source ); + + assertThat( target.getPromotionDate() ).isEqualTo( LocalDate.of( 2018, Month.AUGUST, 5 ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Source.java new file mode 100644 index 0000000000..1be37556b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Source.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.ap.test.bugs._1569.java8; + +import java.util.Date; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private Date promotionDate; + + public Date getPromotionDate() { + return promotionDate; + } + + public void setPromotionDate(Date promotionDate) { + this.promotionDate = promotionDate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Target.java new file mode 100644 index 0000000000..d523517577 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1569/java8/Target.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.ap.test.bugs._1569.java8; + +import java.time.LocalDate; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private LocalDate promotionDate; + + public LocalDate getPromotionDate() { + return promotionDate; + } + + public void setPromotionDate(LocalDate promotionDate) { + this.promotionDate = promotionDate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Issue1576Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Issue1576Mapper.java new file mode 100644 index 0000000000..a1c5a82a3b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Issue1576Mapper.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.ap.test.bugs._1576.java8; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue1576Mapper { + + Issue1576Mapper INSTANCE = Mappers.getMapper( Issue1576Mapper.class ); + + Target map( Source source ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Issue1576Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Issue1576Test.java new file mode 100644 index 0000000000..36f10eb5ad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Issue1576Test.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.ap.test.bugs._1576.java8; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@IssueKey("1576") +@WithClasses( { Issue1576Mapper.class, Source.class, Target.class }) +public class Issue1576Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + Issue1576Mapper.class + ); + + @ProcessorTest + public void testLocalDateTimeIsImported() { + + Issue1576Mapper.INSTANCE.map( new Source() ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Source.java new file mode 100644 index 0000000000..c23bb0359f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Source.java @@ -0,0 +1,57 @@ +/* + * 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.test.bugs._1576.java8; + +import java.util.Date; + +public class Source { + + private Date localDateTime; + private Date localDate; + private String localTime; + private Date zonedDateTime; + private Date instant; + + public Date getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(Date localDateTime) { + this.localDateTime = localDateTime; + } + + public Date getLocalDate() { + return localDate; + } + + public void setLocalDate(Date localDate) { + this.localDate = localDate; + } + + public String getLocalTime() { + return localTime; + } + + public void setLocalTime(String localTime) { + this.localTime = localTime; + } + + public Date getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(Date zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public Date getInstant() { + return instant; + } + + public void setInstant(Date instant) { + this.instant = instant; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Target.java new file mode 100644 index 0000000000..e43ecf0737 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1576/java8/Target.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.ap.test.bugs._1576.java8; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZonedDateTime; + +public class Target { + + private LocalDateTime localDateTime; + private LocalDate localDate; + private LocalTime localTime; + private ZonedDateTime zonedDateTime; + private Instant instant; + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } + + public LocalTime getLocalTime() { + return localTime; + } + + public void setLocalTime(LocalTime localTime) { + this.localTime = localTime; + } + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public Instant getInstant() { + return instant; + } + + public void setInstant(Instant instant) { + this.instant = instant; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.java new file mode 100644 index 0000000000..bfe79c1772 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Book.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.ap.test.bugs._1590; + +public class Book { + + private final String name; + + public Book(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookMapper.java new file mode 100644 index 0000000000..31acd1885a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookMapper.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.ap.test.bugs._1590; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BookMapper { + + BookMapper INSTANCE = Mappers.getMapper( BookMapper.class ); + + Book map(Book book); + + List map(List books); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookShelf.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookShelf.java new file mode 100644 index 0000000000..056b409d4d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookShelf.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.ap.test.bugs._1590; + +import java.util.List; + +public class BookShelf { + private List books; + + public List getBooks() { + return books; + } + + public void setBooks(List books) { + this.books = books; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookShelfMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookShelfMapper.java new file mode 100644 index 0000000000..46f53f6469 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/BookShelfMapper.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.ap.test.bugs._1590; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(uses = BookMapper.class, + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) +public interface BookShelfMapper { + + BookShelfMapper INSTANCE = Mappers.getMapper( BookShelfMapper.class ); + + BookShelf map(BookShelf bookShelf); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java new file mode 100644 index 0000000000..0a9113797c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.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.test.bugs._1590; + +import java.util.Arrays; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + */ +@WithClasses({ + BookMapper.class, + BookShelfMapper.class, + Book.class, + BookShelf.class +}) +@IssueKey("1590") +public class Issue1590Test { + + @ProcessorTest + public void shouldTestMappingLocalDates() { + BookShelf source = new BookShelf(); + source.setBooks( Arrays.asList( new Book("Test") ) ); + + BookShelf target = BookShelfMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getBooks() ).isNotNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1594/Issue1594Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1594/Issue1594Mapper.java new file mode 100644 index 0000000000..fbb99ed529 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1594/Issue1594Mapper.java @@ -0,0 +1,84 @@ +/* + * 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.test.bugs._1594; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1594Mapper { + + Issue1594Mapper INSTANCE = Mappers.getMapper( Issue1594Mapper.class ); + + @Mappings({ + @Mapping(target = "address.country.oid", expression = "java(dto.getFullAddress().split( \"-\" )[0])"), + @Mapping(target = "address.city.oid", expression = "java(dto.getFullAddress().split( \"-\" )[1])"), + }) + Client toClient(Dto dto); + + class Client { + private Address address; + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + class Address { + + private Id country; + private Id city; + + public Id getCountry() { + return country; + } + + public void setCountry(Id country) { + this.country = country; + } + + public Id getCity() { + return city; + } + + public void setCity(Id city) { + this.city = city; + } + } + + class Id { + private String oid; + + public String getOid() { + return oid; + } + + public void setOid(String oid) { + this.oid = oid; + } + } + + class Dto { + private String fullAddress; + + public String getFullAddress() { + return fullAddress; + } + + public void setFullAddress(String fullAddress) { + this.fullAddress = fullAddress; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1594/Issue1594Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1594/Issue1594Test.java new file mode 100644 index 0000000000..0c1fed523e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1594/Issue1594Test.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.test.bugs._1594; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1594") +@WithClasses({ + Issue1594Mapper.class +}) +public class Issue1594Test { + + @ProcessorTest + public void shouldGenerateCorrectMapping() { + Issue1594Mapper.Dto dto = new Issue1594Mapper.Dto(); + dto.setFullAddress( "Switzerland-Zurich" ); + + Issue1594Mapper.Client client = Issue1594Mapper.INSTANCE.toClient( dto ); + + assertThat( client ).isNotNull(); + assertThat( client.getAddress() ).isNotNull(); + assertThat( client.getAddress().getCountry() ).isNotNull(); + assertThat( client.getAddress().getCountry().getOid() ).isEqualTo( "Switzerland" ); + assertThat( client.getAddress().getCity() ).isNotNull(); + assertThat( client.getAddress().getCity().getOid() ).isEqualTo( "Zurich" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/Issue1569BuilderProvider.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/Issue1569BuilderProvider.java new file mode 100644 index 0000000000..92676f9ae0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/Issue1569BuilderProvider.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.test.bugs._1596; + +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +public class Issue1569BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider { + + @Override + protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.toString().endsWith( ".Item" ) ) { + BuilderInfo info = findBuilderInfoForImmutables( typeElement ); + if ( info != null ) { + return info; + } + } + + return super.findBuilderInfo( typeElement ); + } + + protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement) { + TypeElement immutableElement = asImmutableElement( typeElement ); + if ( immutableElement != null ) { + return super.findBuilderInfo( immutableElement ); + } + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/Issue1596Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/Issue1596Test.java new file mode 100644 index 0000000000..96286aa1a1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/Issue1596Test.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.ap.test.bugs._1596; + +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.test.bugs._1596.domain.ImmutableItem; +import org.mapstruct.ap.test.bugs._1596.domain.Item; +import org.mapstruct.ap.test.bugs._1596.dto.ImmutableItemDTO; +import org.mapstruct.ap.test.bugs._1596.dto.ItemDTO; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithServiceImplementations; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + */ +@WithClasses({ + ItemMapper.class, + Item.class, + ImmutableItem.class, + ItemDTO.class, + ImmutableItemDTO.class +}) +@IssueKey("1596") +@WithServiceImplementations( { + @WithServiceImplementation( provides = BuilderProvider.class, value = Issue1569BuilderProvider.class), + @WithServiceImplementation( provides = AccessorNamingStrategy.class, value = ImmutablesAccessorNamingStrategy.class) +}) +public class Issue1596Test { + + @ProcessorTest + public void shouldIncludeBuildType() { + + ItemDTO item = ImmutableItemDTO.builder().id( "test" ).build(); + + Item target = ItemMapper.INSTANCE.map( item ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/ItemMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/ItemMapper.java new file mode 100644 index 0000000000..00f647148b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/ItemMapper.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.ap.test.bugs._1596; + +import org.mapstruct.Builder; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._1596.domain.Item; +import org.mapstruct.ap.test.bugs._1596.dto.ItemDTO; +import org.mapstruct.factory.Mappers; + +@Mapper( builder = @Builder) +public abstract class ItemMapper { + + public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class ); + + public abstract Item map(ItemDTO itemDTO); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/domain/ImmutableItem.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/domain/ImmutableItem.java new file mode 100644 index 0000000000..ed5bb98fa1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/domain/ImmutableItem.java @@ -0,0 +1,162 @@ +/* + * 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.test.bugs._1596.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * Immutable implementation of {@link Item}. + *

      + * Use the builder to create immutable instances: + * {@code ImmutableItem.builder()}. + */ +@SuppressWarnings({"all"}) +public final class ImmutableItem extends Item { + private final String id; + + private ImmutableItem(String id) { + this.id = id; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * Copy the current immutable object by setting a value for the {@link Item#getId() id} attribute. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * @param value A new value for id + * @return A modified copy of the {@code this} object + */ + public final ImmutableItem withId(String value) { + if (this.id == value) return this; + return new ImmutableItem(value); + } + + /** + * This instance is equal to all instances of {@code ImmutableItem} that have equal attribute values. + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if (this == another) return true; + return another instanceof ImmutableItem + && equalTo((ImmutableItem) another); + } + + private boolean equalTo(ImmutableItem another) { + return id.equals(another.id); + } + + /** + * Computes a hash code from attributes: {@code id}. + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += (h << 5) + id.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + "}"; + } + + /** + * Creates an immutable copy of a {@link Item} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItem copyOf(Item instance) { + if (instance instanceof ImmutableItem) { + return (ImmutableItem) instance; + } + return ImmutableItem.builder() + .from(instance) + .build(); + } + + /** + * Creates a builder for {@link ImmutableItem ImmutableItem}. + * @return A new ImmutableItem builder + */ + public static ImmutableItem.Builder builder() { + return new ImmutableItem.Builder(); + } + + /** + * Builds instances of type {@link ImmutableItem ImmutableItem}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static final class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + + private Builder() { + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final Builder from(Item instance) { + id(instance.getId()); + return this; + } + + /** + * Initializes the value for the {@link Item#getId() id} attribute. + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final Builder id(String id) { + this.id = id; + initBits &= ~INIT_BIT_ID; + return this; + } + + /** + * Builds a new {@link ImmutableItem ImmutableItem}. + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItem build() { + if (initBits != 0) { + throw new IllegalStateException(formatRequiredAttributesMessage()); + } + return new ImmutableItem(id); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ((initBits & INIT_BIT_ID) != 0) attributes.add("id"); + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/domain/Item.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/domain/Item.java new file mode 100644 index 0000000000..6c5be03818 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/domain/Item.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.ap.test.bugs._1596.domain; + +public abstract class Item { + public abstract String getId(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/dto/ImmutableItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/dto/ImmutableItemDTO.java new file mode 100644 index 0000000000..699e0f9ded --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/dto/ImmutableItemDTO.java @@ -0,0 +1,182 @@ +/* + * 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.test.bugs._1596.dto; + +import java.util.ArrayList; +import java.util.List; + +/** + * Immutable implementation of {@link ItemDTO}. + *

      + * Use the builder to create immutable instances: + * {@code ImmutableItemDTO.builder()}. + */ +@SuppressWarnings({ "all" }) +public final class ImmutableItemDTO extends ItemDTO { + private final String id; + + private ImmutableItemDTO(String id) { + this.id = id; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * Copy the current immutable object by setting a value for the {@link ItemDTO#getId() id} attribute. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * + * @param value A new value for id + * + * @return A modified copy of the {@code this} object + */ + public final ImmutableItemDTO withId(String value) { + if ( this.id == value ) { + return this; + } + return new ImmutableItemDTO( value ); + } + + /** + * This instance is equal to all instances of {@code ImmutableItemDTO} that have equal attribute values. + * + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if ( this == another ) { + return true; + } + return another instanceof ImmutableItemDTO + && equalTo( (ImmutableItemDTO) another ); + } + + private boolean equalTo(ImmutableItemDTO another) { + return id.equals( another.id ); + } + + /** + * Computes a hash code from attributes: {@code id}. + * + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += ( h << 5 ) + id.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code ItemDTO} with attribute values. + * + * @return A string representation of the value + */ + @Override + public String toString() { + return "ItemDTO{" + + "id=" + id + + "}"; + } + + /** + * Creates an immutable copy of a {@link ItemDTO} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * + * @param instance The instance to copy + * + * @return A copied immutable ItemDTO instance + */ + public static ImmutableItemDTO copyOf(ItemDTO instance) { + if ( instance instanceof ImmutableItemDTO ) { + return (ImmutableItemDTO) instance; + } + return ImmutableItemDTO.builder() + .from( instance ) + .build(); + } + + /** + * Creates a builder for {@link ImmutableItemDTO ImmutableItemDTO}. + * + * @return A new ImmutableItemDTO builder + */ + public static ImmutableItemDTO.Builder builder() { + return new ImmutableItemDTO.Builder(); + } + + /** + * Builds instances of type {@link ImmutableItemDTO ImmutableItemDTO}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static final class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + + private Builder() { + } + + /** + * Fill a builder with attribute values from the provided {@code ItemDTO} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * + * @param instance The instance from which to copy values + * + * @return {@code this} builder for use in a chained invocation + */ + public final Builder from(ItemDTO instance) { + id( instance.getId() ); + return this; + } + + /** + * Initializes the value for the {@link ItemDTO#getId() id} attribute. + * + * @param id The value for id + * + * @return {@code this} builder for use in a chained invocation + */ + public final Builder id(String id) { + this.id = id; + initBits &= ~INIT_BIT_ID; + return this; + } + + /** + * Builds a new {@link ImmutableItemDTO ImmutableItemDTO}. + * + * @return An immutable instance of ItemDTO + * + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItemDTO build() { + if ( initBits != 0 ) { + throw new IllegalStateException( formatRequiredAttributesMessage() ); + } + return new ImmutableItemDTO( id ); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ( ( initBits & INIT_BIT_ID ) != 0 ) { + attributes.add( "id" ); + } + return "Cannot build ItemDTO, some of required attributes are not set " + attributes; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/dto/ItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/dto/ItemDTO.java new file mode 100644 index 0000000000..9cbed4ae54 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1596/dto/ItemDTO.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.ap.test.bugs._1596.dto; + +public abstract class ItemDTO { + public abstract String getId(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Book.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Book.java new file mode 100644 index 0000000000..0bfeb08e1b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Book.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._1608; + +/** + * @author Filip Hrisafov + */ +public class Book { + + private String isbn; + + private int issueYear; + + public String getIsbn() { + return isbn; + } + + public void setIsbn(String isbn) { + this.isbn = isbn; + } + + public int getIssueYear() { + return issueYear; + } + + public void setIssueYear(int issueYear) { + this.issueYear = issueYear; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/BookDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/BookDto.java new file mode 100644 index 0000000000..7244cc1c6a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/BookDto.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.ap.test.bugs._1608; + +/** + * @author Filip Hrisafov + */ +public class BookDto { + + private final String isbn; + private final int issueYear; + + public BookDto(String isbn, int issueYear) { + this.isbn = isbn; + this.issueYear = issueYear; + } + + public String getIsbn() { + return isbn; + } + + public int getIssueYear() { + return issueYear; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String isbn; + + private int issueYear; + + public String getIsbn() { + return isbn; + } + + public Builder isbn(String isbn) { + this.isbn = isbn; + return this; + } + + public int getIssueYear() { + return issueYear; + } + + public Builder setIssueYear(int issueYear) { + this.issueYear = issueYear; + return this; + } + + public BookDto build() { + return new BookDto( isbn, issueYear ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Issue1608Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Issue1608Mapper.java new file mode 100644 index 0000000000..f36f9739f9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Issue1608Mapper.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.ap.test.bugs._1608; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1608Mapper { + + Issue1608Mapper INSTANCE = Mappers.getMapper( Issue1608Mapper.class ); + + BookDto map(Book source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Issue1608Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Issue1608Test.java new file mode 100644 index 0000000000..528bc9b91b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1608/Issue1608Test.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.test.bugs._1608; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1608") +@WithClasses({ + Issue1608Mapper.class, + Book.class, + BookDto.class +}) +public class Issue1608Test { + + @ProcessorTest + public void shouldCorrectlyUseFluentSettersStartingWithIs() { + + Book book = new Book(); + book.setIsbn( "978-3-16-148410-0" ); + book.setIssueYear( 2018 ); + BookDto bookDto = Issue1608Mapper.INSTANCE.map( book ); + + assertThat( bookDto.getIsbn() ).isEqualTo( "978-3-16-148410-0" ); + assertThat( bookDto.getIssueYear() ).isEqualTo( 2018 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Issue1648Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Issue1648Mapper.java new file mode 100644 index 0000000000..713de86997 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Issue1648Mapper.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.ap.test.bugs._1648; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue1648Mapper { + + Issue1648Mapper INSTANCE = Mappers.getMapper( Issue1648Mapper.class ); + + @Mapping(target = "targetValue", source = "sourceValue") + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Issue1648Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Issue1648Test.java new file mode 100644 index 0000000000..5dfe65d77e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Issue1648Test.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.test.bugs._1648; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1648") +@WithClasses({ + Issue1648Mapper.class, + Source.class, + Target.class, +}) +public class Issue1648Test { + + @ProcessorTest + public void shouldCorrectlyMarkSourceAsUsed() { + Source source = new Source(); + source.setSourceValue( "value" ); + + Target target = Issue1648Mapper.INSTANCE.map( source ); + + assertThat( target.getTargetValue() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Source.java new file mode 100644 index 0000000000..5d9f207bf0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Source.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.ap.test.bugs._1648; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String sourceValue; + + public String getSourceValue() { + return sourceValue; + } + + public void setSourceValue(String sourceValue) { + this.sourceValue = sourceValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Target.java new file mode 100644 index 0000000000..0225e9540f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1648/Target.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.ap.test.bugs._1648; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String targetValue; + + public String getTargetValue() { + return targetValue; + } + + public void setTargetValue(String targetValue) { + this.targetValue = targetValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/A.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/A.java new file mode 100644 index 0000000000..692f72282a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/A.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.ap.test.bugs._1650; + +public class A { + private B b; + + public B getB() { + return b; + } + + public void setB(B b) { + this.b = b; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/AMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/AMapper.java new file mode 100644 index 0000000000..ba6eeda861 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/AMapper.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.ap.test.bugs._1650; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface AMapper { + + AMapper INSTANCE = Mappers.getMapper( AMapper.class ); + + @Mapping(target = "cPrime", source = "b.c") + APrime toAPrime(A a, @MappingTarget APrime mappingTarget); + + CPrime toCPrime(C c, @MappingTarget CPrime mappingTarget); + + @Mapping(target = "cPrime", source = "b.c") + APrime toAPrime(A a); + + CPrime toCPrime(C c); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/APrime.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/APrime.java new file mode 100644 index 0000000000..625816f179 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/APrime.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.ap.test.bugs._1650; + +public class APrime { + + private CPrime cPrime; + + public CPrime getcPrime() { + return cPrime; + } + + public void setcPrime(CPrime cPrime) { + this.cPrime = cPrime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/B.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/B.java new file mode 100644 index 0000000000..924858c8d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/B.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.ap.test.bugs._1650; + +public class B { + private C c; + + public C getC() { + return c; + } + + public void setC(C c) { + this.c = c; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java new file mode 100644 index 0000000000..dbbd74ec95 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.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.ap.test.bugs._1650; + +public class C { + + private final int value; + + public C(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.java new file mode 100644 index 0000000000..db504189f8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/CPrime.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.ap.test.bugs._1650; + +public class CPrime { + + private int value; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.java new file mode 100644 index 0000000000..33237e726c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/Issue1650Test.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.ap.test.bugs._1650; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1650") +@WithClasses({ + AMapper.class, + A.class, + B.class, + C.class, + APrime.class, + CPrime.class +}) +public class Issue1650Test { + + @ProcessorTest + public void shouldCompile() { + + A a = new A(); + a.setB( new B() ); + a.getB().setC( new C( 10 ) ); + + APrime aPrime = new APrime(); + + // update mapping + AMapper.INSTANCE.toAPrime( a, aPrime ); + assertThat( aPrime.getcPrime() ).isNotNull(); + + // create mapping + APrime aPrime1 = AMapper.INSTANCE.toAPrime( a ); + assertThat( aPrime1.getcPrime() ).isNotNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Issue1660Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Issue1660Mapper.java new file mode 100644 index 0000000000..512455970c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Issue1660Mapper.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.ap.test.bugs._1660; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue1660Mapper { + + Issue1660Mapper INSTANCE = Mappers.getMapper( Issue1660Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Issue1660Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Issue1660Test.java new file mode 100644 index 0000000000..7de37e6b43 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Issue1660Test.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.test.bugs._1660; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1660") +@WithClasses({ + Issue1660Mapper.class, + Source.class, + Target.class, +}) +public class Issue1660Test { + + @ProcessorTest + public void shouldNotUseStaticMethods() { + Source source = new Source(); + source.setValue( "source" ); + Target target = Issue1660Mapper.INSTANCE.map( source ); + + assertThat( target.getValue() ).isEqualTo( "source" ); + assertThat( Target.getStaticValue() ).isEqualTo( "targetStatic" ); + assertThat( Target.getOtherStaticValues() ).containsExactly( "targetOtherStatic" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Source.java new file mode 100644 index 0000000000..bd324f2f14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Source.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.test.bugs._1660; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static String getStaticValue() { + return "sourceStatic"; + } + + public String getOtherStaticValue() { + return "sourceOtherStatic"; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Target.java new file mode 100644 index 0000000000..2e2e3cd27d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1660/Target.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.ap.test.bugs._1660; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private static String staticValue = "targetStatic"; + private static List otherStaticValues = new ArrayList<>( Collections.singletonList( "targetOtherStatic" ) ); + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static String getStaticValue() { + return staticValue; + } + + public static void setStaticValue(String staticValue) { + Target.staticValue = staticValue; + } + + public static List getOtherStaticValues() { + return otherStaticValues; + } + + public static void addOtherStaticValue(String otherStaticValue) { + Target.otherStaticValues.add( otherStaticValue ); + } + + public static void setOtherStaticValues(List otherStaticValues) { + Target.otherStaticValues = otherStaticValues; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Issue1665Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Issue1665Mapper.java new file mode 100644 index 0000000000..1087ba4c1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Issue1665Mapper.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.ap.test.bugs._1665; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Arne Seime + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue1665Mapper { + + Issue1665Mapper INSTANCE = Mappers.getMapper( Issue1665Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Issue1665Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Issue1665Test.java new file mode 100644 index 0000000000..169e01029a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Issue1665Test.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._1665; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Arne Seime + */ +@IssueKey("1665") +@WithClasses({ + Issue1665Mapper.class, + Source.class, + Target.class, +}) +public class Issue1665Test { + + @ProcessorTest + public void shouldBoxIntPrimitive() { + Source source = new Source(); + List values = new ArrayList<>(); + values.add( 10 ); + source.setValue( values ); + + Target target = Issue1665Mapper.INSTANCE.map( source ); + + assertThat( target.getValue().size() ).isEqualTo( source.getValue().size() ); + assertThat( target.getValue().get( 0 ) ).isEqualTo( source.getValue().get( 0 ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Source.java new file mode 100644 index 0000000000..f89ed4436d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Source.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.ap.test.bugs._1665; + +import java.util.List; + +/** + * @author Arne Seime + */ +public class Source { + + private List value; + + public List getValue() { + return value; + } + + public void setValue(List value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Target.java new file mode 100644 index 0000000000..7c2cf5620d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1665/Target.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.ap.test.bugs._1665; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Arne Seime + */ +public class Target { + + private List value = new ArrayList<>(); + + public void addValue(int v) { + value.add( v ); + } + + public List getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Issue1681Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Issue1681Mapper.java new file mode 100644 index 0000000000..a8fcc2ec79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Issue1681Mapper.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.ap.test.bugs._1681; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1681Mapper { + + Issue1681Mapper INSTANCE = Mappers.getMapper( Issue1681Mapper.class ); + + Target update(@MappingTarget Target target, Source source); + + @Mapping(target = "builderValue", source = "value") + Target update(@MappingTarget Target.Builder targetBuilder, Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Issue1681Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Issue1681Test.java new file mode 100644 index 0000000000..901794dea0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Issue1681Test.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.ap.test.bugs._1681; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1681") +@WithClasses({ + Issue1681Mapper.class, + Source.class, + Target.class +}) +public class Issue1681Test { + + @ProcessorTest + public void shouldCompile() { + Target target = new Target( "before" ); + Source source = new Source(); + source.setValue( "after" ); + + Target updatedTarget = Issue1681Mapper.INSTANCE.update( target, source ); + + assertThat( updatedTarget ).isSameAs( target ); + assertThat( updatedTarget.getValue() ).isEqualTo( "after" ); + } + + @ProcessorTest + public void shouldCompileWithBuilder() { + Target.Builder targetBuilder = Target.builder(); + targetBuilder.builderValue( "before" ); + Source source = new Source(); + source.setValue( "after" ); + + Target updatedTarget = Issue1681Mapper.INSTANCE.update( targetBuilder, source ); + + assertThat( updatedTarget ).isNotNull(); + assertThat( updatedTarget.getValue() ).isEqualTo( "after" ); + assertThat( targetBuilder.getBuilderValue() ).isEqualTo( "after" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Source.java new file mode 100644 index 0000000000..384dbcca1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Source.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.ap.test.bugs._1681; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Target.java new file mode 100644 index 0000000000..5a392e1968 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1681/Target.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._1681; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String builderValue; + + public String getBuilderValue() { + return builderValue; + } + + public Builder builderValue(String builderValue) { + this.builderValue = builderValue; + return this; + } + + public Target build() { + return new Target( builderValue ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/ContactDataDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/ContactDataDTO.java new file mode 100644 index 0000000000..1d3fe69dfa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/ContactDataDTO.java @@ -0,0 +1,56 @@ +/* + * 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.test.bugs._1685; + +import java.util.List; + +public class ContactDataDTO { + private String email; + private String phone; + private String address; + private List preferences; + private String[] settings; + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getPreferences() { + return preferences; + } + + public void setPreferences(List preferences) { + this.preferences = preferences; + } + + public String[] getSettings() { + return settings; + } + + public void setSettings(String[] settings) { + this.settings = settings; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/Issue1685Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/Issue1685Test.java new file mode 100644 index 0000000000..eccef6df2f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/Issue1685Test.java @@ -0,0 +1,114 @@ +/* + * 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.test.bugs._1685; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1685") +@WithClasses({ + User.class, + UserDTO.class, + UserMapper.class, + ContactDataDTO.class +}) +public class Issue1685Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + UserMapper.class + ); + + @ProcessorTest + public void testSetToNullWhenNVPMSSetToNull() { + + User target = new User(); + target.setAddress( "address" ); + target.setEmail( "email" ); + target.setName( "name" ); + target.setPhone( 12345 ); + target.addPreference( "preference" ); + target.setSettings( new String[]{ "test" } ); + + UserDTO source = new UserDTO(); + source.setContactDataDTO( new ContactDataDTO() ); + source.getContactDataDTO().setAddress( "newAddress" ); + source.getContactDataDTO().setEmail( null ); + source.getContactDataDTO().setPhone( null ); + source.setName( null ); + + UserMapper.INSTANCE.updateUserFromUserDTO( source, target ); + + assertThat( target.getAddress() ).isEqualTo( "newAddress" ); + assertThat( target.getEmail() ).isNull(); + assertThat( target.getPhone() ).isNull(); + assertThat( target.getName() ).isNull(); + assertThat( target.getPreferences() ).containsOnly( "preference" ); + assertThat( target.getSettings() ).isNull(); + } + + @ProcessorTest + public void testIgnoreWhenNVPMSIgnore() { + + User target = new User(); + target.setAddress( "address" ); + target.setEmail( "email" ); + target.setName( "name" ); + target.setPhone( 12345 ); + target.addPreference( "preference" ); + target.setSettings( new String[]{ "test" } ); + + UserDTO source = new UserDTO(); + source.setContactDataDTO( new ContactDataDTO() ); + source.getContactDataDTO().setAddress( "newAddress" ); + source.getContactDataDTO().setEmail( null ); + source.getContactDataDTO().setPhone( null ); + source.setName( null ); + + UserMapper.INSTANCE.updateUserFromUserAndIgnoreDTO( source, target ); + + assertThat( target.getAddress() ).isEqualTo( "newAddress" ); + assertThat( target.getEmail() ).isEqualTo( "email" ); + assertThat( target.getPhone() ).isEqualTo( 12345 ); + assertThat( target.getName() ).isEqualTo( "name" ); + assertThat( target.getPreferences() ).containsOnly( "preference" ); + assertThat( target.getSettings() ).containsExactly( "test" ); + } + + @ProcessorTest + public void testSetToDefaultWhenNVPMSSetToDefault() { + + User target = new User(); + target.setAddress( "address" ); + target.setEmail( "email" ); + target.setName( "name" ); + target.setPhone( 12345 ); + target.addPreference( "preference" ); + target.setSettings( new String[]{ "test" } ); + + UserDTO source = new UserDTO(); + source.setContactDataDTO( new ContactDataDTO() ); + source.getContactDataDTO().setAddress( "newAddress" ); + source.getContactDataDTO().setEmail( null ); + source.getContactDataDTO().setPhone( null ); + source.setName( null ); + + UserMapper.INSTANCE.updateUserFromUserAndDefaultDTO( source, target ); + + assertThat( target.getAddress() ).isEqualTo( "newAddress" ); + assertThat( target.getEmail() ).isEqualTo( "" ); + assertThat( target.getPhone() ).isEqualTo( 0 ); + assertThat( target.getName() ).isEqualTo( "" ); + assertThat( target.getPreferences() ).containsOnly( "preference" ); + assertThat( target.getSettings() ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/User.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/User.java new file mode 100644 index 0000000000..3216465fc4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/User.java @@ -0,0 +1,68 @@ +/* + * 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.test.bugs._1685; + +import java.util.ArrayList; +import java.util.List; + +public class User { + private String name; + private String email; + private Integer phone; + private String address; + private List preferences = new ArrayList<>( ); + private String[] settings; + + 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; + } + + public Integer getPhone() { + return phone; + } + + public void setPhone(Integer phone) { + this.phone = phone; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public void addPreference(String preference) { + preferences.add( preference ); + } + + public List getPreferences() { + return preferences; + } + + public String[] getSettings() { + return settings; + } + + public void setSettings(String[] settings) { + this.settings = settings; + } +} + + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserDTO.java new file mode 100644 index 0000000000..0ea035ee87 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserDTO.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.ap.test.bugs._1685; + +public class UserDTO { + private String name; + private ContactDataDTO contactDataDTO; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ContactDataDTO getContactDataDTO() { + return contactDataDTO; + } + + public void setContactDataDTO(ContactDataDTO contactDataDTO) { + this.contactDataDTO = contactDataDTO; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserMapper.java new file mode 100644 index 0000000000..4c838a3d55 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1685/UserMapper.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.ap.test.bugs._1685; + +import org.mapstruct.BeanMapping; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper( collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED ) +public interface UserMapper { + + UserMapper INSTANCE = Mappers.getMapper( UserMapper.class ); + + @Mappings({ + @Mapping(target = "contactDataDTO.email", source = "email" ), + @Mapping(target = "contactDataDTO.phone", source = "phone"), + @Mapping(target = "contactDataDTO.address", source = "address"), + @Mapping(target = "contactDataDTO.preferences", source = "preferences"), + @Mapping(target = "contactDataDTO.settings", source = "settings") + }) + UserDTO userToUserDTO(User user); + + @InheritInverseConfiguration + void updateUserFromUserDTO(UserDTO userDTO, @MappingTarget User user); + + @InheritInverseConfiguration + @BeanMapping( nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE ) + void updateUserFromUserAndIgnoreDTO(UserDTO userDTO, @MappingTarget User user); + + @InheritInverseConfiguration + @BeanMapping( nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT ) + void updateUserFromUserAndDefaultDTO(UserDTO userDTO, @MappingTarget User user); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1698/Erroneous1698Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1698/Erroneous1698Mapper.java new file mode 100644 index 0000000000..d158239778 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1698/Erroneous1698Mapper.java @@ -0,0 +1,111 @@ +/* + * 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.test.bugs._1698; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Erroneous1698Mapper { + + Erroneous1698Mapper INSTANCE = Mappers.getMapper( Erroneous1698Mapper.class ); + + Target map(Source source); + + @Mapping(target = "value", source = "source") + Cat mapToCat(String source); + + @Mapping(target = "value", source = "source") + Dog mapToDog(String source); + + class Source { + + private String cat; + private String dog; + private String rabbit; + + public String getCat() { + return cat; + } + + public void setCat(String cat) { + this.cat = cat; + } + + public String getDog() { + return dog; + } + + public void setDog(String dog) { + this.dog = dog; + } + + public String getRabbit() { + return rabbit; + } + + public void setRabbit(String rabbit) { + this.rabbit = rabbit; + } + } + + class Target { + + private Cat cat; + private Dog dog; + private Rabbit rabbit; + + public Cat getCat() { + return cat; + } + + public void setCat(Cat cat) { + this.cat = cat; + } + + public Dog getDog() { + return dog; + } + + public void setDog(Dog dog) { + this.dog = dog; + } + + public Rabbit getRabbit() { + return rabbit; + } + + public void setRabbit(Rabbit rabbit) { + this.rabbit = rabbit; + } + } + + class Animal { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Cat extends Animal { + + } + + class Dog extends Animal { + + } + + class Rabbit extends Animal { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1698/Issue1698Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1698/Issue1698Test.java new file mode 100644 index 0000000000..c5af6d923f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1698/Issue1698Test.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.test.bugs._1698; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +@IssueKey("1698") +@WithClasses(Erroneous1698Mapper.class) +public class Issue1698Test { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = Erroneous1698Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + message = "Can't map property \"String rabbit\" to \"Erroneous1698Mapper.Rabbit rabbit\". " + + "Consider to declare/implement a mapping method: \"Erroneous1698Mapper.Rabbit map(String value)\".") + }) + + public void testErrorMessage() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1707/Converter.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1707/Converter.java new file mode 100644 index 0000000000..45659b9d6f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1707/Converter.java @@ -0,0 +1,89 @@ +/* + * 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.test.bugs._1707; + +import java.util.Set; +import java.util.stream.Stream; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; + +@Mapper +public abstract class Converter { + + public abstract Set convert(Stream source); + + public abstract Target[] convertArray(Stream source); + + @Mapping( target = "custom", ignore = true ) + public abstract Target convert(Source source); + + @AfterMapping + public void addCustomValue(@MappingTarget Set targetList) { + targetList.forEach( t -> t.custom = true ); + } + + @AfterMapping + public void addCustomValue(@MappingTarget Target[] targetArray) { + for ( Target target : targetArray ) { + target.custom = true; + } + } + + public static final class Source { + private String text; + private int number; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + } + + public static class Target { + private String text; + private int number; + private boolean custom; + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + + public boolean isCustom() { + return custom; + } + + public void setCustom(boolean custom) { + this.custom = custom; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1707/Issue1707Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1707/Issue1707Test.java new file mode 100644 index 0000000000..9798ea1bb6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1707/Issue1707Test.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.ap.test.bugs._1707; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@IssueKey("1707") +@WithClasses({ + Converter.class +}) +public class Issue1707Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + Converter.class + ); + + @ProcessorTest + public void codeShouldBeGeneratedCorrectly() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1714/Issue1714Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1714/Issue1714Mapper.java new file mode 100644 index 0000000000..3985606d94 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1714/Issue1714Mapper.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.ap.test.bugs._1714; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue1714Mapper { + + Issue1714Mapper INSTANCE = Mappers.getMapper( Issue1714Mapper.class ); + + @Mapping(target = "seasonNumber", source = "programInstance", qualifiedByName = "getSeasonNumber") + OfferEntity map(OnDemand offerStatusDTO); + + @Named("getTitle") + default String mapTitle(Program programInstance) { + return "dont care"; + } + + @Named("getSeasonNumber") + default Integer mapSeasonNumber(Program programInstance) { + return 1; + } + + class OfferEntity { + + private String seasonNumber; + + public String getSeasonNumber() { + return seasonNumber; + } + + public void setSeasonNumber(String seasonNumber) { + this.seasonNumber = seasonNumber; + } + + } + + class OnDemand { + + private Program programInstance; + + public Program getProgramInstance() { + return programInstance; + } + + public void setProgramInstance(Program programInstance) { + this.programInstance = programInstance; + } + } + + class Program { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1714/Issue1714Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1714/Issue1714Test.java new file mode 100644 index 0000000000..4a2e42ad61 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1714/Issue1714Test.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.ap.test.bugs._1714; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1714") +@WithClasses({ + Issue1714Mapper.class +}) +public class Issue1714Test { + + @ProcessorTest + public void codeShouldBeGeneratedCorrectly() { + + Issue1714Mapper.OnDemand source = new Issue1714Mapper.OnDemand(); + source.setProgramInstance( new Issue1714Mapper.Program() ); + + Issue1714Mapper.OfferEntity result = Issue1714Mapper.INSTANCE.map( source ); + + assertThat( result.getSeasonNumber() ).isEqualTo( "1" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Mapper.java new file mode 100644 index 0000000000..f4ac6978f6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Mapper.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.ap.test.bugs._1719; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue1719Mapper { + + Issue1719Mapper INSTANCE = Mappers.getMapper( Issue1719Mapper.class ); + + @Mapping(target = "targetElements", source = "sourceElements") + void map(Source source, @MappingTarget Target target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.java new file mode 100644 index 0000000000..7a8665e49f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Issue1719Test.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.ap.test.bugs._1719; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +@IssueKey("1719") +@WithClasses({ + Source.class, + SourceElement.class, + Target.class, + TargetElement.class +}) +public class Issue1719Test { + + /** + * For adder methods MapStuct cannot generate an update method. MapStruct would cannot know how to remove objects + * from the child-parent relation. It cannot even assume that the collection can be cleared at forehand. + * Therefore, the only sensible choice is for MapStruct to create a create method for the target elements. + */ + @ProcessorTest + @WithClasses(Issue1719Mapper.class) + public void testShouldGiveNoErrorMessage() { + Source source = new Source(); + source.getSourceElements().add( new SourceElement( 1, "jim" ) ); + source.getSourceElements().add( new SourceElement( 2, "alice" ) ); + + Target target = new Target(); + TargetElement bob = new TargetElement( 1, "bob" ); + target.addTargetElement( bob ); + TargetElement louise = new TargetElement( 3, "louise" ); + target.addTargetElement( louise ); + + Issue1719Mapper.INSTANCE.map( source, target ); + + assertThat( target.getTargetElements() ).hasSize( 3 ); + assertThat( target.getTargetElements() ) + .extracting( TargetElement::getId, TargetElement::getName ) + .containsOnly( + tuple( 1, "bob" ), + tuple( 2, "alice" ), + tuple( 3, "louise" ) + ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Source.java new file mode 100644 index 0000000000..f3e6b1ea20 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Source.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.ap.test.bugs._1719; + +import java.util.HashSet; +import java.util.Set; + +public class Source { + + private Set sourceElements = new HashSet<>(); + + public Set getSourceElements() { + return sourceElements; + } + + public void setSourceElements(Set sourceElements) { + this.sourceElements = sourceElements; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/SourceElement.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/SourceElement.java new file mode 100644 index 0000000000..fbfe00e829 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/SourceElement.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.ap.test.bugs._1719; + +import java.util.Objects; + +public class SourceElement { + + private int id; + private String name; + + public SourceElement(int id, String name) { + this.id = id; + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SourceElement that = (SourceElement) o; + return id == that.id; + } + + @Override + public int hashCode() { + return Objects.hash( id ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Target.java new file mode 100644 index 0000000000..9d14a9a143 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/Target.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.test.bugs._1719; + +import java.util.HashSet; +import java.util.Set; + +public class Target { + + private Set targetElements = new HashSet<>(); + + public Set getTargetElements() { + return targetElements; + } + + public void setTargetElements(Set targetElements) { + this.targetElements = targetElements; + } + + public TargetElement addTargetElement(TargetElement element) { + element.updateTarget( this ); + getTargetElements().add( element ); + return element; + } + + public TargetElement removeTargetElement(TargetElement element) { + element.updateTarget( null ); + getTargetElements().remove( element ); + return element; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/TargetElement.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/TargetElement.java new file mode 100644 index 0000000000..92d1970af2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1719/TargetElement.java @@ -0,0 +1,70 @@ +/* + * 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.test.bugs._1719; + +import java.util.Objects; + +public class TargetElement { + + private int id; + private String name; + private Target target; + + public TargetElement() { + } + + public TargetElement(int id, String name) { + this.id = id; + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Target getTarget() { + return target; + } + + /** + * intentionally not a setter, to not further complicate this test case. + * + * @param target + */ + public void updateTarget(Target target) { + this.target = target; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + TargetElement that = (TargetElement) o; + return id == that.id; + } + + @Override + public int hashCode() { + return Objects.hash( id ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Issue1738Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Issue1738Mapper.java new file mode 100644 index 0000000000..79147f2247 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Issue1738Mapper.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.ap.test.bugs._1738; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1738Mapper { + + Issue1738Mapper INSTANCE = Mappers.getMapper( Issue1738Mapper.class ); + + @Mapping(target = "value", source = "nested.value") + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Issue1738Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Issue1738Test.java new file mode 100644 index 0000000000..e0755312fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Issue1738Test.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.test.bugs._1738; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1738") +@WithClasses({ + Issue1738Mapper.class, + Source.class, + Target.class +}) +public class Issue1738Test { + + @ProcessorTest + public void shouldGenerateCorrectSourceNestedMapping() { + Source source = new Source(); + Source.Nested nested = new Source.Nested<>(); + source.setNested( nested ); + nested.setValue( 100L ); + + Target target = Issue1738Mapper.INSTANCE.map( source ); + + assertThat( target.getValue() ).isEqualTo( 100L ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Source.java new file mode 100644 index 0000000000..7f67c6db6d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Source.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.test.bugs._1738; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public static class Nested { + + private T value; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Target.java new file mode 100644 index 0000000000..931ccb85b7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1738/Target.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.ap.test.bugs._1738; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private Number value; + + public Number getValue() { + return value; + } + + public void setValue(Number value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Issue1742Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Issue1742Mapper.java new file mode 100644 index 0000000000..55b5dd03c4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Issue1742Mapper.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.ap.test.bugs._1742; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1742Mapper { + + void update(@MappingTarget Target target, Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Issue1742Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Issue1742Test.java new file mode 100644 index 0000000000..6c64e0c261 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Issue1742Test.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.ap.test.bugs._1742; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1742") +@WithClasses( { + Issue1742Mapper.class, + NestedSource.class, + NestedTarget.class, + Source.class, + Target.class, +} ) +public class Issue1742Test { + + @ProcessorTest + public void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/NestedSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/NestedSource.java new file mode 100644 index 0000000000..9506c946b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/NestedSource.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.ap.test.bugs._1742; + +/** + * @author Filip Hrisafov + */ +public class NestedSource { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/NestedTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/NestedTarget.java new file mode 100644 index 0000000000..82dbbd91b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/NestedTarget.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._1742; + +/** + * @author Filip Hrisafov + */ +public class NestedTarget { + + private String value; + + public NestedTarget() { + + } + + public NestedTarget(Builder builder) { + this.value = getValue(); + } + + public static Builder builder() { + return new Builder(); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static class Builder { + + private String value; + + public Builder value(String value) { + this.value = value; + return this; + } + + public NestedTarget create() { + return new NestedTarget(this); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Source.java new file mode 100644 index 0000000000..e6a7b54c86 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Source.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.ap.test.bugs._1742; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private NestedSource nested; + + public NestedSource getNested() { + return nested; + } + + public void setNested(NestedSource nested) { + this.nested = nested; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Target.java new file mode 100644 index 0000000000..ab320b606f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1742/Target.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.ap.test.bugs._1742; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private NestedTarget nested; + + public NestedTarget getNested() { + return nested; + } + + public void setNested(NestedTarget nested) { + this.nested = nested; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Holder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Holder.java new file mode 100644 index 0000000000..cd35297d73 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Holder.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.test.bugs._1751; + +/** + * @author Filip Hrisafov + */ +public class Holder { + + private final T value; + + public Holder(T value) { + this.value = value; + } + + public T getValue() { + return value; + } + + // If empty is considered as a builder creation method, this method would be the build method and would + // lead to a stackoverflow + @SuppressWarnings("unused") + public Holder duplicate() { + return new Holder<>( value ); + } + + // This method should not be considered as builder creation method + @SuppressWarnings("unused") + public static Holder empty() { + return new Holder<>( null ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Issue1751Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Issue1751Mapper.java new file mode 100644 index 0000000000..62fb66dc0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Issue1751Mapper.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.ap.test.bugs._1751; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1751Mapper { + + Issue1751Mapper INSTANCE = Mappers.getMapper( Issue1751Mapper.class ); + + Target map(Source source); + + default Holder mapToHolder(Source source) { + return new Holder<>( this.map( source ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Issue1751Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Issue1751Test.java new file mode 100644 index 0000000000..e35424761a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Issue1751Test.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.test.bugs._1751; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1772") +@WithClasses({ + Holder.class, + Issue1751Mapper.class, + Source.class, + Target.class +}) +public class Issue1751Test { + + @ProcessorTest + public void name() { + Source source = new Source(); + source.setValue( "some value" ); + + Holder targetHolder = Issue1751Mapper.INSTANCE.mapToHolder( source ); + + assertThat( targetHolder.getValue() ) + .extracting( Target::getValue ) + .isEqualTo( "some value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Source.java new file mode 100644 index 0000000000..d69e688d79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Source.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.ap.test.bugs._1751; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Target.java new file mode 100644 index 0000000000..a6b2ca91b5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1751/Target.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.ap.test.bugs._1751; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Issue1772Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Issue1772Mapper.java new file mode 100644 index 0000000000..867385ce25 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Issue1772Mapper.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.ap.test.bugs._1772; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue1772Mapper { + + Issue1772Mapper INSTANCE = Mappers.getMapper( Issue1772Mapper.class ); + + @Mapping(target = "nestedTarget.doubleNestedTarget", source = "nestedSource.doublyNestedSourceField" ) + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Issue1772Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Issue1772Test.java new file mode 100644 index 0000000000..e05938e2f9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Issue1772Test.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.test.bugs._1772; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + */ +@IssueKey("1772") +@WithClasses({ + Issue1772Mapper.class, + Source.class, + Target.class, +}) +public class Issue1772Test { + + @ProcessorTest + public void shouldCorrectlyMarkSourceAsUsed() { + + Source source = new Source(); + source.setNestedSource( new Source.NestedSource() ); + source.getNestedSource().setDoublyNestedSourceField( 5d ); + + Target target = Issue1772Mapper.INSTANCE.map( source ); + + assertThat( target.getNestedTarget().getDoubleNestedTarget() ).isEqualTo( 5d ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Source.java new file mode 100644 index 0000000000..076ae8a410 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Source.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.test.bugs._1772; + +/** + * @author Sjaak Derksen + */ +public class Source { + + private NestedSource nestedSource; + + public NestedSource getNestedSource() { + return nestedSource; + } + + public void setNestedSource(NestedSource nestedSource) { + this.nestedSource = nestedSource; + } + + public static class NestedSource { + + private double doublyNestedSourceField; + + public double getDoublyNestedSourceField() { + return doublyNestedSourceField; + } + + public void setDoublyNestedSourceField(double doublyNestedSourceField) { + this.doublyNestedSourceField = doublyNestedSourceField; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Target.java new file mode 100644 index 0000000000..6ad876acd1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Target.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.test.bugs._1772; + +/** + * @author Sjaak Derksen + */ +public class Target { + + private NestedTarget nestedTarget; + + public NestedTarget getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(NestedTarget nestedTarget) { + this.nestedTarget = nestedTarget; + } + + public static class NestedTarget { + + private double doubleNestedTarget; + + public double getDoubleNestedTarget() { + return doubleNestedTarget; + } + + public void setDoubleNestedTarget(double doubleNestedTarget) { + this.doubleNestedTarget = doubleNestedTarget; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1788/Issue1788Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1788/Issue1788Mapper.java new file mode 100644 index 0000000000..d265f00192 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1788/Issue1788Mapper.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.ap.test.bugs._1788; + +import org.mapstruct.Mapper; + +@Mapper +public interface Issue1788Mapper { + + Container toContainer(Container.Type type); + + class Container { + public enum Type { + ONE, TWO + } + + //CHECKSTYLE:OFF + public Type type; + //CHECKSTYLE:ON + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1788/Issue1788Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1788/Issue1788Test.java new file mode 100644 index 0000000000..bd5d687ae0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1788/Issue1788Test.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.ap.test.bugs._1788; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey( "1788" ) +@WithClasses( + Issue1788Mapper.class +) +public class Issue1788Test { + + @ProcessorTest + public void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Config.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Config.java new file mode 100644 index 0000000000..41fe78a0d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Config.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._1790; + +import org.mapstruct.MapperConfig; +import org.mapstruct.NullValueCheckStrategy; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +public interface Issue1790Config { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Mapper.java new file mode 100644 index 0000000000..72330b7356 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Mapper.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.ap.test.bugs._1790; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = Issue1790Config.class, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface Issue1790Mapper { + + Issue1790Mapper INSTANCE = Mappers.getMapper( Issue1790Mapper.class ); + + void toExistingCar(@MappingTarget Target target, Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Test.java new file mode 100644 index 0000000000..c92d240cbf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Issue1790Test.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.test.bugs._1790; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1790") +@WithClasses({ + Issue1790Config.class, + Issue1790Mapper.class, + Source.class, + Target.class, +}) +public class Issue1790Test { + + @ProcessorTest + public void shouldProperlyApplyNullValuePropertyMappingStrategyWhenInheriting() { + Target target = new Target(); + target.setName( "My name is set" ); + + Source source = new Source(); + + Issue1790Mapper.INSTANCE.toExistingCar( target, source ); + + assertThat( target.getName() ).isEqualTo( "My name is set" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Source.java new file mode 100644 index 0000000000..af6c94b279 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Source.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.ap.test.bugs._1790; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Target.java new file mode 100644 index 0000000000..facbbaf48c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1790/Target.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.ap.test.bugs._1790; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Customer.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Customer.java new file mode 100644 index 0000000000..1d7dce9aa2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Customer.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.ap.test.bugs._1797; + +import java.util.EnumSet; + +/** + * @author Filip Hrisafov + */ +public class Customer { + + public enum Type { + ONE, TWO + } + + private final EnumSet types; + + public Customer(EnumSet types) { + this.types = types; + } + + public EnumSet getTypes() { + return types; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/CustomerDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/CustomerDto.java new file mode 100644 index 0000000000..fd51ddc6cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/CustomerDto.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.ap.test.bugs._1797; + +import java.util.EnumSet; + +/** + * @author Filip Hrisafov + */ +public class CustomerDto { + + public enum Type { + ONE, TWO + } + + private EnumSet types; + + public EnumSet getTypes() { + return types; + } + + public void setTypes(EnumSet types) { + this.types = types; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Issue1797Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Issue1797Mapper.java new file mode 100644 index 0000000000..f5a0e4162a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Issue1797Mapper.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.ap.test.bugs._1797; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1797Mapper { + + Issue1797Mapper INSTANCE = Mappers.getMapper( Issue1797Mapper.class ); + + CustomerDto map(Customer customer); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Issue1797Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Issue1797Test.java new file mode 100644 index 0000000000..bd9fcb92e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Issue1797Test.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.test.bugs._1797; + +import java.util.EnumSet; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1797") +@WithClasses({ + Customer.class, + CustomerDto.class, + Issue1797Mapper.class +}) +public class Issue1797Test { + + @ProcessorTest + public void shouldCorrectlyMapEnumSetToEnumSet() { + + Customer customer = new Customer( EnumSet.of( Customer.Type.ONE ) ); + + CustomerDto customerDto = Issue1797Mapper.INSTANCE.map( customer ); + + assertThat( customerDto.getTypes() ).containsExactly( CustomerDto.Type.ONE ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Mapper.java new file mode 100644 index 0000000000..7b695dd146 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Mapper.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.ap.test.bugs._1799; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1799Mapper { + + Issue1799Mapper INSTANCE = Mappers.getMapper( Issue1799Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Test.java new file mode 100644 index 0000000000..b3234cbcfc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Issue1799Test.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.test.bugs._1799; + +import java.util.Date; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue1799Mapper.class, + Source.class, + Target.class, +}) +@IssueKey("1799") +public class Issue1799Test { + + @ProcessorTest + public void fluentJavaBeanStyleSettersShouldWork() { + Target target = Issue1799Mapper.INSTANCE.map( new Source( new Date( 150 ), "Switzerland" ) ); + + assertThat( target.getSettlementDate() ).isEqualTo( new Date( 150 ) ); + assertThat( target.getGetawayLocation() ).isEqualTo( "Switzerland" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Source.java new file mode 100644 index 0000000000..dc7153a2f2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Source.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.test.bugs._1799; + +import java.util.Date; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final Date settlementDate; + private final String getawayLocation; + + public Source(Date settlementDate, String getawayLocation) { + this.settlementDate = settlementDate; + this.getawayLocation = getawayLocation; + } + + public Date getSettlementDate() { + return settlementDate; + } + + public String getGetawayLocation() { + return getawayLocation; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Target.java new file mode 100644 index 0000000000..992e3d2286 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1799/Target.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.ap.test.bugs._1799; + +import java.util.Date; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final Date settlementDate; + private final String getawayLocation; + + public Target(Builder builder) { + this.settlementDate = builder.settlementDate; + this.getawayLocation = builder.getawayLocation; + } + + public Date getSettlementDate() { + return settlementDate; + } + + public String getGetawayLocation() { + return getawayLocation; + } + + public static Target.Builder builder() { + return new Builder(); + } + + public static class Builder { + + private Date settlementDate; + private String getawayLocation; + + public Builder settlementDate(Date settlementDate) { + this.settlementDate = settlementDate; + return this; + } + + public Builder getawayLocation(String getawayLocation) { + this.getawayLocation = getawayLocation; + return this; + } + + public Target build() { + return new Target( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801BuilderProvider.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801BuilderProvider.java new file mode 100644 index 0000000000..d8b5ccdbb3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801BuilderProvider.java @@ -0,0 +1,96 @@ +/* + * 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.test.bugs._1801; + +import java.util.List; +import java.util.Objects; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +/** + * @author Zhizhi Deng + */ +public class Issue1801BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider { + + @Override + protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.toString().endsWith( ".Item" ) ) { + BuilderInfo info = findBuilderInfoFromInnerBuilderClass( typeElement ); + if ( info != null ) { + return info; + } + } + return super.findBuilderInfo( typeElement ); + } + + /** + * Looks for inner builder class in the Immutable interface / abstract class. + * + * The inner builder class should be be declared with the following line + * + *

      +     *     public static Builder() extends ImmutableItem.Builder { }
      +     * 
      + * + * The Immutable instance should be created with the following line + * + *
      +     *     new Item.Builder().withId("123").build();
      +     * 
      + * + * @see org.mapstruct.ap.test.bugs._1801.domain.Item + * + * @param typeElement + * @return + */ + private BuilderInfo findBuilderInfoFromInnerBuilderClass(TypeElement typeElement) { + if ( shouldIgnore( typeElement ) ) { + return null; + } + + List innerTypes = ElementFilter.typesIn( typeElement.getEnclosedElements() ); + ExecutableElement defaultConstructor = innerTypes.stream() + .filter( this::isBuilderCandidate ) + .map( this::getEmptyArgPublicConstructor ) + .filter( Objects::nonNull ) + .findAny() + .orElse( null ); + + if ( defaultConstructor != null ) { + return new BuilderInfo.Builder() + .builderCreationMethod( defaultConstructor ) + .buildMethod( findBuildMethods( (TypeElement) defaultConstructor.getEnclosingElement(), typeElement ) ) + .build(); + } + return null; + } + + private boolean isBuilderCandidate(TypeElement innerType ) { + TypeElement outerType = (TypeElement) innerType.getEnclosingElement(); + String packageName = this.elementUtils.getPackageOf( outerType ).getQualifiedName().toString(); + Name outerSimpleName = outerType.getSimpleName(); + String builderClassName = packageName + ".Immutable" + outerSimpleName + ".Builder"; + return innerType.getSimpleName().contentEquals( "Builder" ) + && getTypeElement( innerType.getSuperclass() ).getQualifiedName().contentEquals( builderClassName ) + && innerType.getModifiers().contains( Modifier.PUBLIC ); + } + + private ExecutableElement getEmptyArgPublicConstructor(TypeElement builderType) { + return ElementFilter.constructorsIn( builderType.getEnclosedElements() ).stream() + .filter( c -> c.getParameters().isEmpty() ) + .filter( c -> c.getModifiers().contains( Modifier.PUBLIC ) ) + .findAny() + .orElse( null ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801Test.java new file mode 100644 index 0000000000..40b9515305 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/Issue1801Test.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.ap.test.bugs._1801; + +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.test.bugs._1801.domain.ImmutableItem; +import org.mapstruct.ap.test.bugs._1801.domain.Item; +import org.mapstruct.ap.test.bugs._1801.dto.ImmutableItemDTO; +import org.mapstruct.ap.test.bugs._1801.dto.ItemDTO; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithServiceImplementations; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Zhizhi Deng + */ +@WithClasses({ + ItemMapper.class, + Item.class, + ImmutableItem.class, + ItemDTO.class, + ImmutableItemDTO.class +}) +@IssueKey("1801") +@WithServiceImplementations( { + @WithServiceImplementation( provides = BuilderProvider.class, value = Issue1801BuilderProvider.class), + @WithServiceImplementation( provides = AccessorNamingStrategy.class, value = ImmutablesAccessorNamingStrategy.class) +}) +public class Issue1801Test { + + @ProcessorTest + public void shouldIncludeBuilderType() { + + ItemDTO item = ImmutableItemDTO.builder().id( "test" ).build(); + + Item target = ItemMapper.INSTANCE.map( item ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/ItemMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/ItemMapper.java new file mode 100644 index 0000000000..f43bf12ca2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/ItemMapper.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.ap.test.bugs._1801; + +import org.mapstruct.Builder; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._1801.domain.Item; +import org.mapstruct.ap.test.bugs._1801.dto.ItemDTO; +import org.mapstruct.factory.Mappers; + +/** + * @author Zhizhi Deng + */ +@Mapper( builder = @Builder) +public abstract class ItemMapper { + + public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class ); + + public abstract Item map(ItemDTO itemDTO); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/ImmutableItem.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/ImmutableItem.java new file mode 100644 index 0000000000..d5b39cf4dc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/ImmutableItem.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.ap.test.bugs._1801.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * Immutable implementation of {@link Item}. + *

      + * Superclass should expose a static subclass of the Builder to create immutable instance + * {@code public static Builder extends ImmutableItem.Builder}. + * + * @author Zhizhi Deng + */ +@SuppressWarnings({"all"}) +public final class ImmutableItem extends Item { + private final String id; + + private ImmutableItem(String id) { + this.id = id; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * Copy the current immutable object by setting a value for the {@link Item#getId() id} attribute. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * @param value A new value for id + * @return A modified copy of the {@code this} object + */ + public final ImmutableItem withId(String value) { + if (this.id == value) return this; + return new ImmutableItem(value); + } + + /** + * This instance is equal to all instances of {@code ImmutableItem} that have equal attribute values. + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if (this == another) return true; + return another instanceof ImmutableItem + && equalTo((ImmutableItem) another); + } + + private boolean equalTo(ImmutableItem another) { + return id.equals(another.id); + } + + /** + * Computes a hash code from attributes: {@code id}. + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += (h << 5) + id.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + "}"; + } + + /** + * Creates an immutable copy of a {@link Item} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItem copyOf(Item instance) { + if (instance instanceof ImmutableItem) { + return (ImmutableItem) instance; + } + return new Builder() + .from(instance) + .build(); + } + + /** + * Builds instances of type {@link ImmutableItem ImmutableItem}. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + + Builder() { + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final Builder from(Item instance) { + id(instance.getId()); + return this; + } + + /** + * Initializes the value for the {@link Item#getId() id} attribute. + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final Builder id(String id) { + this.id = id; + initBits &= ~INIT_BIT_ID; + return this; + } + + /** + * Builds a new {@link ImmutableItem ImmutableItem}. + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItem build() { + if (initBits != 0) { + throw new IllegalStateException(formatRequiredAttributesMessage()); + } + return new ImmutableItem(id); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ((initBits & INIT_BIT_ID) != 0) attributes.add("id"); + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/Item.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/Item.java new file mode 100644 index 0000000000..e0f3899077 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/domain/Item.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.ap.test.bugs._1801.domain; + +/** + * @author Zhizhi Deng + */ +public abstract class Item { + public abstract String getId(); + + public static class Builder extends ImmutableItem.Builder { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/dto/ImmutableItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/dto/ImmutableItemDTO.java new file mode 100644 index 0000000000..bf10721aa1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/dto/ImmutableItemDTO.java @@ -0,0 +1,184 @@ +/* + * 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.test.bugs._1801.dto; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Immutable implementation of {@link ItemDTO}. + *

      + * Use the builder to create immutable instances: + * {@code ImmutableItemDTO.builder()}. + * + * @author Zhizhi Deng + */ +public final class ImmutableItemDTO extends ItemDTO { + private final String id; + + private ImmutableItemDTO(String id) { + this.id = id; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * Copy the current immutable object by setting a value for the {@link ItemDTO#getId() id} attribute. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * + * @param value A new value for id + * + * @return A modified copy of the {@code this} object + */ + public ImmutableItemDTO withId(String value) { + if ( Objects.equals( this.id, value ) ) { + return this; + } + return new ImmutableItemDTO( value ); + } + + /** + * This instance is equal to all instances of {@code ImmutableItemDTO} that have equal attribute values. + * + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if ( this == another ) { + return true; + } + return another instanceof ImmutableItemDTO + && equalTo( (ImmutableItemDTO) another ); + } + + private boolean equalTo(ImmutableItemDTO another) { + return id.equals( another.id ); + } + + /** + * Computes a hash code from attributes: {@code id}. + * + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += ( h << 5 ) + id.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code ItemDTO} with attribute values. + * + * @return A string representation of the value + */ + @Override + public String toString() { + return "ItemDTO{" + + "id=" + id + + "}"; + } + + /** + * Creates an immutable copy of a {@link ItemDTO} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * + * @param instance The instance to copy + * + * @return A copied immutable ItemDTO instance + */ + public static ImmutableItemDTO copyOf(ItemDTO instance) { + if ( instance instanceof ImmutableItemDTO ) { + return (ImmutableItemDTO) instance; + } + return ImmutableItemDTO.builder() + .from( instance ) + .build(); + } + + /** + * Creates a builder for {@link ImmutableItemDTO ImmutableItemDTO}. + * + * @return A new ImmutableItemDTO builder + */ + public static ImmutableItemDTO.Builder builder() { + return new ImmutableItemDTO.Builder(); + } + + /** + * Builds instances of type {@link ImmutableItemDTO ImmutableItemDTO}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static final class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + + private Builder() { + } + + /** + * Fill a builder with attribute values from the provided {@code ItemDTO} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * + * @param instance The instance from which to copy values + * + * @return {@code this} builder for use in a chained invocation + */ + public Builder from(ItemDTO instance) { + id( instance.getId() ); + return this; + } + + /** + * Initializes the value for the {@link ItemDTO#getId() id} attribute. + * + * @param id The value for id + * + * @return {@code this} builder for use in a chained invocation + */ + public Builder id(String id) { + this.id = id; + initBits &= ~INIT_BIT_ID; + return this; + } + + /** + * Builds a new {@link ImmutableItemDTO ImmutableItemDTO}. + * + * @return An immutable instance of ItemDTO + * + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItemDTO build() { + if ( initBits != 0 ) { + throw new IllegalStateException( formatRequiredAttributesMessage() ); + } + return new ImmutableItemDTO( id ); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ( ( initBits & INIT_BIT_ID ) != 0 ) { + attributes.add( "id" ); + } + return "Cannot build ItemDTO, some of required attributes are not set " + attributes; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/dto/ItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/dto/ItemDTO.java new file mode 100644 index 0000000000..1b8866b5ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1801/dto/ItemDTO.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.ap.test.bugs._1801.dto; + +/** + * @author Zhizhi Deng + */ +public abstract class ItemDTO { + public abstract String getId(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.java new file mode 100644 index 0000000000..406fa7b71b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Mapper.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.ap.test.bugs._1821; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue1821Mapper { + + Issue1821Mapper INSTANCE = Mappers.getMapper( Issue1821Mapper.class ); + + @BeanMapping( resultType = Target.class ) + Target map(Source source); + + @InheritConfiguration( name = "map" ) + ExtendedTarget mapExtended(Source source); + + class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class ExtendedTarget extends Target { + } + + class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.java new file mode 100644 index 0000000000..865ef8f6c1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1821/Issue1821Test.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.ap.test.bugs._1821; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("1797") +@WithClasses( Issue1821Mapper.class ) +public class Issue1821Test { + + @ProcessorTest + public void shouldNotGiveNullPtr() { + Issue1821Mapper.INSTANCE.map( new Issue1821Mapper.Source( "test" ) ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/Issue1826Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/Issue1826Mapper.java new file mode 100644 index 0000000000..126837cbfa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/Issue1826Mapper.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.ap.test.bugs._1826; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue1826Mapper { + + Issue1826Mapper INSTANCE = Mappers.getMapper( Issue1826Mapper.class ); + + @Mapping(target = "content", source = "sourceChild.content") + Target sourceAToTarget(SourceParent sourceParent); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/Issue1826Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/Issue1826Test.java new file mode 100644 index 0000000000..be22569e5b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/Issue1826Test.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.ap.test.bugs._1826; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1826") +@WithClasses({ + SourceParent.class, + SourceChild.class, + Target.class, + Issue1826Mapper.class +}) +public class Issue1826Test { + + @ProcessorTest + public void testNestedPropertyMappingChecksForNull() { + SourceParent sourceParent = new SourceParent(); + sourceParent.setSourceChild( null ); + + Target result = Issue1826Mapper.INSTANCE.sourceAToTarget( sourceParent ); + assertThat( result.getContent() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/SourceChild.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/SourceChild.java new file mode 100644 index 0000000000..4243d444eb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/SourceChild.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.ap.test.bugs._1826; + +public class SourceChild { + + private String content; + private Boolean hasContent = false; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + hasContent = true; + } + + public Boolean hasContent() { + return hasContent; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/SourceParent.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/SourceParent.java new file mode 100644 index 0000000000..65f737d15d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/SourceParent.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.ap.test.bugs._1826; + +public class SourceParent { + + private SourceChild sourceChild; + private Boolean hasSourceChild = false; + + public SourceChild getSourceChild() { + return sourceChild; + } + + public void setSourceChild(SourceChild sourceChild) { + this.sourceChild = sourceChild; + this.hasSourceChild = true; + } + + public Boolean hasSourceChild() { + return hasSourceChild; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/Target.java new file mode 100644 index 0000000000..9d2ec717f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1826/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.ap.test.bugs._1826; + +public class Target { + + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/CompleteAddress.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/CompleteAddress.java new file mode 100644 index 0000000000..7af21d26b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/CompleteAddress.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._1828; + +public class CompleteAddress { + + private String lineOne; + private String lineTwo; + private String city; + private String country; + + public String getLineOne() { + return lineOne; + } + + public void setLineOne(String lineOne) { + this.lineOne = lineOne; + } + + public String getLineTwo() { + return lineTwo; + } + + public void setLineTwo(String lineTwo) { + this.lineTwo = lineTwo; + } + + 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/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/Employee.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/Employee.java new file mode 100644 index 0000000000..e635cd16d2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/Employee.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.test.bugs._1828; + +public class Employee { + + private String name; + private GeneralAddress generalAddress; + private SpecialAddress specialAddress; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public GeneralAddress getGeneralAddress() { + return generalAddress; + } + + public void setGeneralAddress(GeneralAddress generalAddress) { + this.generalAddress = generalAddress; + } + + public SpecialAddress getSpecialAddress() { + return specialAddress; + } + + public void setSpecialAddress(SpecialAddress specialAddress) { + this.specialAddress = specialAddress; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/FirstMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/FirstMapper.java new file mode 100644 index 0000000000..9d4540c70b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/FirstMapper.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.ap.test.bugs._1828; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface FirstMapper { + + FirstMapper INSTANCE = Mappers.getMapper( FirstMapper.class ); + + @Mapping(target = "completeAddress.lineOne", source = "specialAddress.line1") + @Mapping(target = "completeAddress.lineTwo", source = "specialAddress.line2") + @Mapping(target = "completeAddress.city", source = "generalAddress.city") + @Mapping(target = "completeAddress.country", source = "generalAddress.country") + Person mapPerson(Employee employee); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/GeneralAddress.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/GeneralAddress.java new file mode 100644 index 0000000000..e15ddbb3e1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/GeneralAddress.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.ap.test.bugs._1828; + +public class GeneralAddress { + + private String city; + private String country; + + 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/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/Issue1828Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/Issue1828Test.java new file mode 100644 index 0000000000..a3aa5b3581 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/Issue1828Test.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.ap.test.bugs._1828; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1828") +@WithClasses({ + CompleteAddress.class, + Employee.class, + FirstMapper.class, + GeneralAddress.class, + Person.class, + SpecialAddress.class, +}) +public class Issue1828Test { + + @ProcessorTest + public void testMapSpecialAndGeneralAddressSet() { + + Employee employee = new Employee(); + employee.setName( "Mad King" ); + + SpecialAddress specialAddress = new SpecialAddress(); + specialAddress.setLine1( "Building One" ); + specialAddress.setLine2( "Street Two" ); + employee.setSpecialAddress( specialAddress ); + + GeneralAddress generalAddress = new GeneralAddress(); + generalAddress.setCity( "King's Landing" ); + generalAddress.setCountry( "Seven Kingdom" ); + employee.setGeneralAddress( generalAddress ); + + Person person = FirstMapper.INSTANCE.mapPerson( employee ); + assertThat( person.getName() ).isEqualTo( "Mad King" ); + + CompleteAddress completeAddress = person.getCompleteAddress(); + assertThat( completeAddress ).isNotNull(); + assertThat( completeAddress.getLineOne() ).isEqualTo( "Building One" ); + assertThat( completeAddress.getLineTwo() ).isEqualTo( "Street Two" ); + assertThat( completeAddress.getCity() ).isEqualTo( "King's Landing" ); + assertThat( completeAddress.getCountry() ).isEqualTo( "Seven Kingdom" ); + } + + @ProcessorTest + public void testMapGeneralAddressNull() { + + Employee employee = new Employee(); + employee.setName( "Mad King" ); + + SpecialAddress specialAddress = new SpecialAddress(); + specialAddress.setLine1( "Building One" ); + specialAddress.setLine2( "Street Two" ); + employee.setSpecialAddress( specialAddress ); + + Person person = FirstMapper.INSTANCE.mapPerson( employee ); + assertThat( person.getName() ).isEqualTo( "Mad King" ); + + CompleteAddress completeAddress = person.getCompleteAddress(); + assertThat( completeAddress ).isNotNull(); + assertThat( completeAddress.getLineOne() ).isEqualTo( "Building One" ); + assertThat( completeAddress.getLineTwo() ).isEqualTo( "Street Two" ); + assertThat( completeAddress.getCity() ).isNull(); + assertThat( completeAddress.getCountry() ).isNull(); + } + + @ProcessorTest + public void testMapSpecialAddressNull() { + + Employee employee = new Employee(); + employee.setName( "Mad King" ); + + GeneralAddress generalAddress = new GeneralAddress(); + generalAddress.setCity( "King's Landing" ); + generalAddress.setCountry( "Seven Kingdom" ); + employee.setGeneralAddress( generalAddress ); + + Person person = FirstMapper.INSTANCE.mapPerson( employee ); + assertThat( person.getName() ).isEqualTo( "Mad King" ); + + CompleteAddress completeAddress = person.getCompleteAddress(); + assertThat( completeAddress ).isNotNull(); + assertThat( completeAddress.getLineOne() ).isNull(); + assertThat( completeAddress.getLineTwo() ).isNull(); + assertThat( completeAddress.getCity() ).isEqualTo( "King's Landing" ); + assertThat( completeAddress.getCountry() ).isEqualTo( "Seven Kingdom" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/Person.java new file mode 100644 index 0000000000..d23dc86423 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/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.ap.test.bugs._1828; + +public class Person { + + String name; + private CompleteAddress completeAddress; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public CompleteAddress getCompleteAddress() { + return completeAddress; + } + + public void setCompleteAddress(CompleteAddress completeAddress) { + this.completeAddress = completeAddress; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/SpecialAddress.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/SpecialAddress.java new file mode 100644 index 0000000000..e7e806865b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1828/SpecialAddress.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.ap.test.bugs._1828; + +public class SpecialAddress { + + private String line1; + private String line2; + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1881/Issue1881Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1881/Issue1881Test.java new file mode 100644 index 0000000000..21bab07e52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1881/Issue1881Test.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.test.bugs._1881; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1881") +@WithClasses({ + VehicleDtoMapper.class, +}) +public class Issue1881Test { + + @ProcessorTest + public void shouldCompileCorrectly() { + VehicleDtoMapper.VehicleDto vehicle = VehicleDtoMapper.INSTANCE.map( new VehicleDtoMapper.Vehicle( + "Test", + 100, + "SUV" + ) ); + + assertThat( vehicle.getName() ).isEqualTo( "Test" ); + assertThat( vehicle.getVehicleProperties() ).isNotNull(); + assertThat( vehicle.getVehicleProperties().getSize() ).isEqualTo( 100 ); + assertThat( vehicle.getVehicleProperties().getType() ).isEqualTo( "SUV" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1881/VehicleDtoMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1881/VehicleDtoMapper.java new file mode 100644 index 0000000000..79ccf8f3a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1881/VehicleDtoMapper.java @@ -0,0 +1,88 @@ +/* + * 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.test.bugs._1881; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface VehicleDtoMapper { + + VehicleDtoMapper INSTANCE = Mappers.getMapper( VehicleDtoMapper.class ); + + @Mapping(target = "name", source = "name") + @Mapping(target = "vehicleProperties.size", source = "size") + @Mapping(target = "vehicleProperties.type", source = "type") + VehicleDto map(Vehicle vehicle); + + class VehicleDto { + private String name; + private VehiclePropertiesDto vehicleProperties; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public VehiclePropertiesDto getVehicleProperties() { + return vehicleProperties; + } + + public void setVehicleProperties(VehiclePropertiesDto vehicleProperties) { + this.vehicleProperties = vehicleProperties; + } + } + + class Vehicle { + private final String name; + private final int size; + private final String type; + + public Vehicle(String name, int size, String type) { + this.name = name; + this.size = size; + this.type = type; + } + + public String getName() { + return name; + } + + public int getSize() { + return size; + } + + public String getType() { + return type; + } + } + + class VehiclePropertiesDto { + private int size; + private String type; + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/AstModifyingAnnotationProcessorSaysNo.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/AstModifyingAnnotationProcessorSaysNo.java new file mode 100644 index 0000000000..74cbc7a20c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/AstModifyingAnnotationProcessorSaysNo.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.ap.test.bugs._1904; + +import javax.lang.model.type.TypeMirror; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; + +/** + * @author Filip Hrisafov + */ +public class AstModifyingAnnotationProcessorSaysNo implements AstModifyingAnnotationProcessor { + @Override + public boolean isTypeComplete(TypeMirror type) { + if ( type.toString().contains( "CarManualDto" ) ) { + return false; + } + return true; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Mapper.java new file mode 100644 index 0000000000..a4f67cb77b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Mapper.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.ap.test.bugs._1904; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue1904Mapper { + + CarManualDto translateManual(CarManual manual); + + /** + * @author Filip Hrisafov + */ + class CarManual { + + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } + + /** + * @author Filip Hrisafov + */ + class CarManualDto { + + private String content; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Test.java new file mode 100644 index 0000000000..b369765433 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1904/Issue1904Test.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.ap.test.bugs._1904; + +import org.mapstruct.ap.spi.AstModifyingAnnotationProcessor; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1904") +@WithClasses({ + Issue1904Mapper.class, +}) +@WithServiceImplementation( + provides = AstModifyingAnnotationProcessor.class, + value = AstModifyingAnnotationProcessorSaysNo.class +) +public class Issue1904Test { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic( + type = Issue1904Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "No implementation was created for Issue1904Mapper due to having a problem in the erroneous " + + "element org.mapstruct.ap.test.bugs._1904.Issue1904Mapper.CarManualDto. 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." + ) + }) + public void shouldHaveCompilationErrorIfMapperCouldNotBeCreated() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Config.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Config.java new file mode 100644 index 0000000000..d971bce47f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Config.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.test.bugs._1933; + +import org.mapstruct.BeanMapping; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingInheritanceStrategy; + +/** + * @author Sjaak Derksen + */ +@MapperConfig(mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG) +public interface Issue1933Config { + + @BeanMapping(ignoreByDefault = true) + Entity updateEntity(Dto dto); + + class Entity { + //CHECKSTYLE:OFF + public String id; + public int updateCount; + //CHECKSTYLE:ON + } + + class Dto { + //CHECKSTYLE:OFF + public String id; + public int updateCount; + //CHECKSTYLE:ON + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Mapper.java new file mode 100644 index 0000000000..fdaa28c1ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Mapper.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.ap.test.bugs._1933; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper(config = Issue1933Config.class) +public interface Issue1933Mapper { + + Issue1933Mapper INSTANCE = Mappers.getMapper( Issue1933Mapper.class ); + + @Mapping(target = "updateCount", source = "updateCount") + Issue1933Config.Entity map(Issue1933Config.Dto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Test.java new file mode 100644 index 0000000000..5d1ca351f9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1933/Issue1933Test.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.test.bugs._1933; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + */ +@IssueKey("1933") +@WithClasses({ + Issue1933Config.class, + Issue1933Mapper.class +}) +public class Issue1933Test { + + @ProcessorTest + public void shouldIgnoreIdAndMapUpdateCount() { + + Issue1933Config.Dto dto = new Issue1933Config.Dto(); + dto.id = "id"; + dto.updateCount = 5; + + Issue1933Config.Entity entity = Issue1933Mapper.INSTANCE.map( dto ); + + assertThat( entity.id ).isNull(); + assertThat( entity.updateCount ).isEqualTo( 5 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1966/Issue1966Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1966/Issue1966Mapper.java new file mode 100644 index 0000000000..13f4d489b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1966/Issue1966Mapper.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.test.bugs._1966; + +import java.util.Collections; +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper( imports = Collections.class ) +public interface Issue1966Mapper { + + Issue1966Mapper INSTANCE = Mappers.getMapper( Issue1966Mapper.class ); + + @Mapping(target = "previousNames", + nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + defaultExpression = "java(Collections.emptyList())") + Animal toAnimal(AnimalRecord record); + + class AnimalRecord { + + private String[] previousNames; + + public String[] getPreviousNames() { + return previousNames; + } + + public void setPreviousNames(String[] previousNames) { + this.previousNames = previousNames; + } + } + + class Animal { + + private List previousNames; + + public List getPreviousNames() { + return previousNames; + } + + public void setPreviousNames(List previousNames) { + this.previousNames = previousNames; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1966/Issue1966Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1966/Issue1966Test.java new file mode 100644 index 0000000000..528f45594a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1966/Issue1966Test.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.ap.test.bugs._1966; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + */ +@IssueKey("1966") +@WithClasses({ + Issue1966Mapper.class +}) +public class Issue1966Test { + + @ProcessorTest + public void shouldSelectDefaultExpressionEvenWhenSourceInMappingIsNotSpecified() { + + Issue1966Mapper.AnimalRecord dto = new Issue1966Mapper.AnimalRecord(); + + Issue1966Mapper.Animal entity = Issue1966Mapper.INSTANCE.toAnimal( dto ); + + assertThat( entity.getPreviousNames() ).isNotNull(); + assertThat( entity.getPreviousNames() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Car.java new file mode 100644 index 0000000000..87722cdb50 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Car.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.ap.test.bugs._1997; + +/** + * @author Filip Hrisafov + */ +public class Car { + private String model; + + private Car(Builder builder) { + this.model = builder.model; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String model; + + public Builder model(String model) { + this.model = model; + return this; + } + + public Car build() { + return new Car( this ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarDetail.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarDetail.java new file mode 100644 index 0000000000..84472d1263 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarDetail.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.ap.test.bugs._1997; + +/** + * @author Filip Hrisafov + */ +public class CarDetail { + private String model; + + private CarDetail(Builder builder) { + this.model = builder.model; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String model; + + public Builder model(String model) { + this.model = model; + return this; + } + + public CarDetail build() { + return new CarDetail( this ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsurance.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsurance.java new file mode 100644 index 0000000000..83fb4350dc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsurance.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.ap.test.bugs._1997; + +/** + * @author Filip Hrisafov + */ +public class CarInsurance { + private CarDetail detail; + + private CarInsurance(Builder builder) { + this.detail = builder.detail; + } + + public CarDetail getDetail() { + return detail; + } + + public void setDetail(CarDetail detail) { + this.detail = detail; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private CarDetail detail; + + public Builder detail(CarDetail detail) { + this.detail = detail; + return this; + } + + public CarInsurance build() { + return new CarInsurance( this ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsuranceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsuranceMapper.java new file mode 100644 index 0000000000..79369b9a79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/CarInsuranceMapper.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.ap.test.bugs._1997; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CarInsuranceMapper { + + CarInsuranceMapper INSTANCE = Mappers.getMapper( CarInsuranceMapper.class ); + + @Mapping(source = "model", target = "detail.model") + void update(Car source, @MappingTarget CarInsurance target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Issue1997Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Issue1997Test.java new file mode 100644 index 0000000000..5645ca173b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1997/Issue1997Test.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.test.bugs._1997; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Car.class, + CarDetail.class, + CarInsurance.class, + CarInsuranceMapper.class +}) +class Issue1997Test { + + @ProcessorTest + void shouldCorrectCreateIntermediateObjectsWithBuilder() { + Car source = Car.builder().model( "Model S" ).build(); + CarInsurance target = CarInsurance.builder().build(); + assertThat( target.getDetail() ).isNull(); + + CarInsuranceMapper.INSTANCE.update( source, target ); + + assertThat( target.getDetail() ).isNotNull(); + assertThat( target.getDetail().getModel() ).isEqualTo( "Model S" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Entity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Entity.java new file mode 100644 index 0000000000..38ec42ee7d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Entity.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.ap.test.bugs._2001; + +import java.util.Set; + +/** + * @author Filip Hrisafov + */ +public +class Entity { + + private Set extras; + + public Set getExtras() { + return extras; + } + + public void setExtras(Set extras) { + this.extras = extras; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/EntityExtra.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/EntityExtra.java new file mode 100644 index 0000000000..ecd0dbb1f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/EntityExtra.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.ap.test.bugs._2001; + +/** + * @author Filip Hrisafov + */ +public +class EntityExtra { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Form.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Form.java new file mode 100644 index 0000000000..a3656acecf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Form.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.ap.test.bugs._2001; + +/** + * @author Filip Hrisafov + */ +public +class Form { + + private FormExtra[] extras; + + public FormExtra[] getExtras() { + return extras; + } + + public void setExtras(FormExtra[] extras) { + this.extras = extras; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/FormExtra.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/FormExtra.java new file mode 100644 index 0000000000..2ff150f4ff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/FormExtra.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.ap.test.bugs._2001; + +/** + * @author Filip Hrisafov + */ +public +class FormExtra { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Issue2001Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Issue2001Mapper.java new file mode 100644 index 0000000000..272d23bb5d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Issue2001Mapper.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.ap.test.bugs._2001; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2001Mapper { + + Form map(Entity entity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Issue2001Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Issue2001Test.java new file mode 100644 index 0000000000..52525e9e9f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2001/Issue2001Test.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.ap.test.bugs._2001; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2001") +@WithClasses( { + Entity.class, + EntityExtra.class, + Form.class, + FormExtra.class, + Issue2001Mapper.class +} ) +public class Issue2001Test { + + @ProcessorTest + public void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Issue2018Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Issue2018Mapper.java new file mode 100644 index 0000000000..b75450ce25 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Issue2018Mapper.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.ap.test.bugs._2018; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2018Mapper { + + Issue2018Mapper INSTANCE = Mappers.getMapper( Issue2018Mapper.class ); + + @Mapping(target = "some_value", source = "someValue") + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Issue2018Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Issue2018Test.java new file mode 100644 index 0000000000..68719b9ff0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Issue2018Test.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.test.bugs._2018; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2018") +@WithClasses({ + Issue2018Mapper.class, + Source.class, + Target.class +}) +public class Issue2018Test { + + @ProcessorTest + public void shouldGenerateCorrectCode() { + Source source = new Source(); + source.setSomeValue( "value" ); + + Target target = Issue2018Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getSome_value() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Source.java new file mode 100644 index 0000000000..767952da1a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Source.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.ap.test.bugs._2018; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String someValue; + + public String getSomeValue() { + return someValue; + } + + public void setSomeValue(String someValue) { + this.someValue = someValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Target.java new file mode 100644 index 0000000000..1d8c1ae3b2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2018/Target.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.ap.test.bugs._2018; + +/** + * @author Filip Hrisafov + */ +public class Target { + + // CHECKSTYLE:OFF + private String some_value; + + public String getSome_value() { + return some_value; + } + + public void setSome_value(String some_value) { + this.some_value = some_value; + } + // CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2021/Issue2021Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2021/Issue2021Mapper.java new file mode 100644 index 0000000000..4627f22323 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2021/Issue2021Mapper.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.ap.test.bugs._2021; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +@DecoratedWith(Issue2021Mapper.Decorator.class) +public interface Issue2021Mapper { + + abstract class Decorator implements Issue2021Mapper { + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2021/Issue2021Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2021/Issue2021Test.java new file mode 100644 index 0000000000..bb6ef326b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2021/Issue2021Test.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.ap.test.bugs._2021; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2021") +@WithClasses({ + Issue2021Mapper.class +}) +public class Issue2021Test { + + @ProcessorTest + public void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/Issue2023Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/Issue2023Mapper.java new file mode 100644 index 0000000000..a2fafb7a2d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/Issue2023Mapper.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.ap.test.bugs._2023; + +import java.util.UUID; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2023Mapper { + + Issue2023Mapper INSTANCE = Mappers.getMapper( Issue2023Mapper.class ); + + @Mapping(target = "correlationId", source = "correlationId", defaultExpression = "java(UUID.randomUUID())") + NewPersonRequest createRequest(PersonDto person, UUID correlationId); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/Issue2023Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/Issue2023Test.java new file mode 100644 index 0000000000..99c97d9eb5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/Issue2023Test.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._2023; + +import java.util.UUID; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2023") +@WithClasses({ + Issue2023Mapper.class, + NewPersonRequest.class, + PersonDto.class, +}) +public class Issue2023Test { + + @ProcessorTest + public void shouldUseDefaultExpressionCorrectly() { + PersonDto person = new PersonDto(); + person.setName( "John" ); + person.setEmail( "john@doe.com" ); + + NewPersonRequest request = Issue2023Mapper.INSTANCE.createRequest( person, null ); + + assertThat( request ).isNotNull(); + assertThat( request.getName() ).isEqualTo( "John" ); + assertThat( request.getEmail() ).isEqualTo( "john@doe.com" ); + assertThat( request.getCorrelationId() ).isNotNull(); + + UUID correlationId = UUID.randomUUID(); + request = Issue2023Mapper.INSTANCE.createRequest( person, correlationId ); + + assertThat( request ).isNotNull(); + assertThat( request.getName() ).isEqualTo( "John" ); + assertThat( request.getEmail() ).isEqualTo( "john@doe.com" ); + assertThat( request.getCorrelationId() ).isEqualTo( correlationId ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/NewPersonRequest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/NewPersonRequest.java new file mode 100644 index 0000000000..98f3d0b5b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/NewPersonRequest.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.ap.test.bugs._2023; + +import java.util.UUID; + +/** + * @author Filip Hrisafov + */ +public class NewPersonRequest { + + private String name; + private String email; + private UUID correlationId; + + 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; + } + + public UUID getCorrelationId() { + return correlationId; + } + + public void setCorrelationId(UUID correlationId) { + this.correlationId = correlationId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/PersonDto.java new file mode 100644 index 0000000000..d2551c50c6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2023/PersonDto.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.ap.test.bugs._2023; + +/** + * @author Filip Hrisafov + */ +public class PersonDto { + + 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/processor/src/test/java/org/mapstruct/ap/test/bugs/_2042/Issue2402Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2042/Issue2402Mapper.java new file mode 100644 index 0000000000..3fe52aaafd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2042/Issue2402Mapper.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.ap.test.bugs._2042; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2402Mapper { + + Issue2402Mapper INSTANCE = Mappers.getMapper( Issue2402Mapper.class ); + + @Mapping(target = ".", source = "source.info") + Target map(Source source, String other); + + class Target { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class Source { + + private final Info info; + + public Source(Info info) { + this.info = info; + } + + public Info getInfo() { + return info; + } + } + + class Info { + private final String name; + + public Info(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2042/Issue2402Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2042/Issue2402Test.java new file mode 100644 index 0000000000..9d1d18dac6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2042/Issue2402Test.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.ap.test.bugs._2042; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2402") +@WithClasses({ + Issue2402Mapper.class +}) +public class Issue2402Test { + + @ProcessorTest + public void shouldCompile() { + Issue2402Mapper.Target target = Issue2402Mapper.INSTANCE. + map( + new Issue2402Mapper.Source( new Issue2402Mapper.Info( "test" ) ), + "other test" + ); + + assertThat( target.getName() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2077/Issue2077ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2077/Issue2077ErroneousMapper.java new file mode 100644 index 0000000000..9d38e8bf45 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2077/Issue2077ErroneousMapper.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.test.bugs._2077; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +/** + * @author Sjaak Derksen + */ +@Mapper( unmappedTargetPolicy = ReportingPolicy.ERROR ) +public interface Issue2077ErroneousMapper { + + @Mapping(target = "s1", defaultValue = "xyz" ) + Target map(String source); + + class Target { + + private String s1; + + public String getS1() { + return s1; + } + + public void setS1(String s1) { + this.s1 = s1; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2077/Issue2077Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2077/Issue2077Test.java new file mode 100644 index 0000000000..607c1137f8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2077/Issue2077Test.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.test.bugs._2077; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static javax.tools.Diagnostic.Kind.ERROR; + +/** + * @author Sjaak Derksen + */ +@IssueKey("2077") +public class Issue2077Test { + + @ProcessorTest + @WithClasses(Issue2077ErroneousMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = Issue2077ErroneousMapper.class, + kind = ERROR, + line = 18, + message = "The type of parameter \"source\" has no property named \"s1\". Please define the source " + + "property explicitly.") + } + ) + public void shouldNotCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101AdditionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101AdditionalMapper.java new file mode 100644 index 0000000000..ea745d6bc1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101AdditionalMapper.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.ap.test.bugs._2101; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2101AdditionalMapper { + + Issue2101AdditionalMapper INSTANCE = Mappers.getMapper( Issue2101AdditionalMapper.class ); + + @Mapping(target = "value1", source = "value.nestedValue1") + @Mapping(target = "value2", source = "value.nestedValue2") + @Mapping(target = "value3", source = "valueThrowOffPath") + Target map1(Source source); + + @InheritConfiguration + @Mapping(target = "value2", source = "value.nestedValue1") + @Mapping(target = "value3", constant = "test") + Target map2(Source source); + + //CHECKSTYLE:OFF + class Source { + public String valueThrowOffPath; + public NestedSource value; + } + + class Target { + public String value1; + public String value2; + public String value3; + } + + class NestedSource { + public String nestedValue1; + public String nestedValue2; + } + //CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101Mapper.java new file mode 100644 index 0000000000..5a1f01fa72 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101Mapper.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.ap.test.bugs._2101; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2101Mapper { + + Issue2101Mapper INSTANCE = Mappers.getMapper( Issue2101Mapper.class ); + + @Mapping(target = "value1", source = "codeValue1") + @Mapping(target = "value2", source = "codeValue2") + Source map(Target target); + + @InheritInverseConfiguration + @Mapping(target = "codeValue1.code", constant = "c1") + @Mapping(target = "codeValue1.value", source = "value1") + @Mapping(target = "codeValue2.code", constant = "c2") + @Mapping(target = "codeValue2.value", source = "value2") + Target map(Source source); + + default String mapFrom( CodeValuePair cv ) { + return cv.code; + } + + //CHECKSTYLE:OFF + class Source { + public String value1; + public String value2; + } + + class Target { + public CodeValuePair codeValue1; + public CodeValuePair codeValue2; + } + + class CodeValuePair { + public String code; + public String value; + } + //CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101Test.java new file mode 100644 index 0000000000..96c3f31b12 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2101Test.java @@ -0,0 +1,80 @@ +/* + * 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.test.bugs._2101; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2101") +public class Issue2101Test { + + @ProcessorTest + @WithClasses(Issue2101Mapper.class) + public void shouldMap() { + + Issue2101Mapper.Source source = new Issue2101Mapper.Source(); + source.value1 = "v1"; + source.value2 = "v2"; + + Issue2101Mapper.Target target = Issue2101Mapper.INSTANCE.map( source ); + + assertThat( target.codeValue1.code ).isEqualTo( "c1" ); + assertThat( target.codeValue1.value ).isEqualTo( "v1" ); + assertThat( target.codeValue2.code ).isEqualTo( "c2" ); + assertThat( target.codeValue2.value ).isEqualTo( "v2" ); + + } + + @ProcessorTest + @WithClasses(Issue2101AdditionalMapper.class) + public void shouldMapSomeAdditionalTests1() { + Issue2101AdditionalMapper.Source source = new Issue2101AdditionalMapper.Source(); + source.value = new Issue2101AdditionalMapper.NestedSource(); + source.value.nestedValue1 = "value1"; + source.value.nestedValue2 = "value2"; + source.valueThrowOffPath = "value3"; + + Issue2101AdditionalMapper.Target target = Issue2101AdditionalMapper.INSTANCE.map1( source ); + assertThat( target.value1 ).isEqualTo( "value1" ); + assertThat( target.value2 ).isEqualTo( "value2" ); + assertThat( target.value3 ).isEqualTo( "value3" ); + } + + @ProcessorTest + @WithClasses(Issue2101AdditionalMapper.class) + public void shouldMapSomeAdditionalTests2() { + Issue2101AdditionalMapper.Source source = new Issue2101AdditionalMapper.Source(); + source.value = new Issue2101AdditionalMapper.NestedSource(); + source.value.nestedValue1 = "value1"; + source.value.nestedValue2 = "value2"; + source.valueThrowOffPath = "value3"; + + Issue2101AdditionalMapper.Target target = Issue2101AdditionalMapper.INSTANCE.map2( source ); + assertThat( target.value1 ).isEqualTo( "value1" ); + assertThat( target.value2 ).isEqualTo( "value1" ); + assertThat( target.value3 ).isEqualTo( "test" ); + + } + + @ProcessorTest + @WithClasses(Issue2102IgnoreAllButMapper.class) + public void shouldApplyIgnoreAllButTemplateOfMethod1() { + + Issue2102IgnoreAllButMapper.Source source = new Issue2102IgnoreAllButMapper.Source(); + source.value1 = "value1"; + source.value2 = "value2"; + + Issue2102IgnoreAllButMapper.Target target = Issue2102IgnoreAllButMapper.INSTANCE.map1( source ); + assertThat( target.value1 ).isEqualTo( "value1" ); + + target = Issue2102IgnoreAllButMapper.INSTANCE.map2( source ); + assertThat( target.value1 ).isEqualTo( "value2" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2102IgnoreAllButMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2102IgnoreAllButMapper.java new file mode 100644 index 0000000000..9634ed310b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2101/Issue2102IgnoreAllButMapper.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.test.bugs._2101; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2102IgnoreAllButMapper { + + Issue2102IgnoreAllButMapper INSTANCE = Mappers.getMapper( Issue2102IgnoreAllButMapper.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping(target = "value1") // but do map value1 + Target map1(Source source); + + @InheritConfiguration + @Mapping(target = "value1", source = "value2" ) + Target map2(Source source); + + //CHECKSTYLE:OFF + class Source { + public String value1; + public String value2; + } + + class Target { + public String value1; + } + //CHECKSTYLE:ON + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Issue2109Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Issue2109Mapper.java new file mode 100644 index 0000000000..7542c2c0cf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Issue2109Mapper.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.ap.test.bugs._2109; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2109Mapper { + + Issue2109Mapper INSTANCE = Mappers.getMapper( Issue2109Mapper.class ); + + Target map(Source source); + + @Mapping(target = "data", defaultExpression = "java(new byte[0])") + Target mapWithEmptyData(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Issue2109Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Issue2109Test.java new file mode 100644 index 0000000000..bc1deed110 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Issue2109Test.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.ap.test.bugs._2109; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2109") +@WithClasses({ + Issue2109Mapper.class, + Source.class, + Target.class, +}) +public class Issue2109Test { + + @ProcessorTest + public void shouldCorrectlyMapArrayInConstructorMapping() { + Target target = Issue2109Mapper.INSTANCE.map( new Source( 100L, new byte[] { 100, 120, 40, 40 } ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( 100L ); + assertThat( target.getData() ).containsExactly( 100, 120, 40, 40 ); + + target = Issue2109Mapper.INSTANCE.map( new Source( 50L, null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( 50L ); + assertThat( target.getData() ).isNull(); + + target = Issue2109Mapper.INSTANCE.mapWithEmptyData( new Source( 100L, new byte[] { 100, 120, 40, 40 } ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( 100L ); + assertThat( target.getData() ).containsExactly( 100, 120, 40, 40 ); + + target = Issue2109Mapper.INSTANCE.mapWithEmptyData( new Source( 50L, null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( 50L ); + assertThat( target.getData() ).isEmpty(); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Source.java new file mode 100644 index 0000000000..a0aba8ce8b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Source.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.ap.test.bugs._2109; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final Long id; + private final byte[] data; + + public Source(Long id, byte[] data) { + this.id = id; + this.data = data; + } + + public Long getId() { + return id; + } + + public byte[] getData() { + return data; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Target.java new file mode 100644 index 0000000000..2e44bc21b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2109/Target.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.ap.test.bugs._2109; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final Long id; + private final byte[] data; + + public Target(Long id, byte[] data) { + this.id = id; + this.data = data; + } + + public Long getId() { + return id; + } + + public byte[] getData() { + return data; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2111/Issue2111Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2111/Issue2111Mapper.java new file mode 100644 index 0000000000..9bf63385fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2111/Issue2111Mapper.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.ap.test.bugs._2111; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +import static java.util.Collections.singletonList; + +@Mapper +public interface Issue2111Mapper { + + @Mapping(target = "strs", source = "ex", qualifiedByName = "wrap") + DTO map(UseExample from); + + @Named("wrap") + default String mapExample(Example ex) { + return ex.name; + } + + @Named("wrap") + default List wrapInList(T t) { + return singletonList( t ); + } + + //CHECKSTYLE:OFF + class Example { + public String name; + } + + class UseExample { + public Example ex; + } + + class DTO { + public List strs; + } + //CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2111/Issue2111Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2111/Issue2111Test.java new file mode 100644 index 0000000000..a3006ceb9c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2111/Issue2111Test.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.ap.test.bugs._2111; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Sjaak Derksen + */ +@IssueKey("2111") +@WithClasses( Issue2111Mapper.class ) +public class Issue2111Test { + + @ProcessorTest + public void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2117/Issue2117Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2117/Issue2117Mapper.java new file mode 100644 index 0000000000..06dd72fafd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2117/Issue2117Mapper.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.test.bugs._2117; + +import java.nio.file.AccessMode; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2117Mapper { + + Issue2117Mapper INSTANCE = Mappers.getMapper( Issue2117Mapper.class ); + + @Mapping(target = "accessMode", source = "accessMode") + Target toTarget(AccessMode accessMode, String otherSource); + + class Target { + // CHECKSTYLE:OFF + public AccessMode accessMode; + // CHECKSTYLE ON + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2117/Issue2117Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2117/Issue2117Test.java new file mode 100644 index 0000000000..984aa2daa9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2117/Issue2117Test.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.ap.test.bugs._2117; + +import java.nio.file.AccessMode; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2117") +@WithClasses({ + Issue2117Mapper.class +}) +public class Issue2117Test { + + @ProcessorTest + public void shouldCompile() { + + Issue2117Mapper.Target target = Issue2117Mapper.INSTANCE.toTarget( AccessMode.READ, null ); + + assertThat( target ).isNotNull(); + assertThat( target.accessMode ).isEqualTo( AccessMode.READ ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2121/Issue2121Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2121/Issue2121Mapper.java new file mode 100644 index 0000000000..f61300c0ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2121/Issue2121Mapper.java @@ -0,0 +1,51 @@ +/* + * 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.test.bugs._2121; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2121Mapper { + + Issue2121Mapper INSTANCE = Mappers.getMapper( Issue2121Mapper.class ); + + Target map(Source source); + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + + private final SourceEnum value; + + public Source(SourceEnum value) { + this.value = value; + } + + public SourceEnum getValue() { + return value; + } + } + + enum SourceEnum { + VALUE1, + VALUE2 + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2121/Issue2121Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2121/Issue2121Test.java new file mode 100644 index 0000000000..24bb88450b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2121/Issue2121Test.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.ap.test.bugs._2121; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2121") +@WithClasses(Issue2121Mapper.class) +public class Issue2121Test { + + @ProcessorTest + public void shouldCompile() { + Issue2121Mapper mapper = Issue2121Mapper.INSTANCE; + + Issue2121Mapper.Target target = mapper.map( new Issue2121Mapper.Source( Issue2121Mapper.SourceEnum.VALUE1 ) ); + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "VALUE1" ); + + target = mapper.map( new Issue2121Mapper.Source( null ) ); + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Method2MethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Method2MethodMapper.java new file mode 100644 index 0000000000..033a314af2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Method2MethodMapper.java @@ -0,0 +1,103 @@ +/* + * 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.test.bugs._2122; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Bandowski + */ +@Mapper +public interface Issue2122Method2MethodMapper { + + Issue2122Method2MethodMapper INSTANCE = Mappers.getMapper( Issue2122Method2MethodMapper.class ); + + @Mapping(target = "embeddedTarget", source = "value") + @Mapping(target = "embeddedMapTarget", source = "value") + @Mapping(target = "embeddedListListTarget", source = "value") + Target toTarget(Source source); + + EmbeddedTarget toEmbeddedTarget(String value); + + default List singleEntry(T entry) { + return Collections.singletonList( entry ); + } + + default List> singleNestedListEntry(T entry) { + return Collections.singletonList( Collections.singletonList( entry ) ); + } + + default HashMap singleEntryMap(T entry) { + HashMap result = new HashMap<>( ); + result.put( "test", entry ); + return result; + } + + class Source { + String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + List embeddedTarget; + + Map embeddedMapTarget; + + List> embeddedListListTarget; + + public List getEmbeddedTarget() { + return embeddedTarget; + } + + public void setEmbeddedTarget(List embeddedTarget) { + this.embeddedTarget = embeddedTarget; + } + + public Map getEmbeddedMapTarget() { + return embeddedMapTarget; + } + + public void setEmbeddedMapTarget( Map embeddedMapTarget) { + this.embeddedMapTarget = embeddedMapTarget; + } + + public List> getEmbeddedListListTarget() { + return embeddedListListTarget; + } + + public void setEmbeddedListListTarget( + List> embeddedListListTarget) { + this.embeddedListListTarget = embeddedListListTarget; + } + } + + class EmbeddedTarget { + String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Method2TypeConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Method2TypeConversionMapper.java new file mode 100644 index 0000000000..43d40aa772 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Method2TypeConversionMapper.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.ap.test.bugs._2122; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface Issue2122Method2TypeConversionMapper { + + Issue2122Method2TypeConversionMapper INSTANCE = Mappers.getMapper( Issue2122Method2TypeConversionMapper.class ); + + @Mapping(target = "value", source = "strings") + Target toTarget(Source source); + + default T toFirstElement(List entry) { + return entry.get( 0 ); + } + + class Source { + List strings; + + public List getStrings() { + return strings; + } + + public void setStrings(List strings) { + this.strings = strings; + } + } + + class Target { + Integer value; + + public Integer getValue() { + return value; + } + + public void setValue(Integer value) { + this.value = value; + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Test.java new file mode 100644 index 0000000000..f0a82d8f0a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122Test.java @@ -0,0 +1,76 @@ +/* + * 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.test.bugs._2122; + +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Bandowski + */ +@IssueKey("2122") +public class Issue2122Test { + + @ProcessorTest + @WithClasses( Issue2122Method2MethodMapper.class ) + public void shouldMapMethod2Method() { + Issue2122Method2MethodMapper.Source source = new Issue2122Method2MethodMapper.Source(); + source.setValue( "value" ); + + Issue2122Method2MethodMapper.Target target = Issue2122Method2MethodMapper.INSTANCE.toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getEmbeddedTarget() ).isNotNull(); + assertThat( target.getEmbeddedTarget() ).hasSize( 1 ) + .element( 0 ) + .extracting( Issue2122Method2MethodMapper.EmbeddedTarget::getValue ).isEqualTo( "value" ); + assertThat( target.getEmbeddedMapTarget() ).isNotNull(); + assertThat( target.getEmbeddedMapTarget() ).hasSize( 1 ); + assertThat( target.getEmbeddedMapTarget().get( "test" ) ) + .extracting( Issue2122Method2MethodMapper.EmbeddedTarget::getValue ).isEqualTo( "value" ); + assertThat( target.getEmbeddedListListTarget() ).isNotNull(); + assertThat( target.getEmbeddedListListTarget() ).hasSize( 1 ); + assertThat( target.getEmbeddedListListTarget().get( 0 ) ).hasSize( 1 ) + .element( 0 ) + .extracting( Issue2122Method2MethodMapper.EmbeddedTarget::getValue ).isEqualTo( "value" ); + } + + @ProcessorTest + @WithClasses( Issue2122TypeConversion2MethodMapper.class ) + public void shouldMapTypeConversion2Method() { + Issue2122TypeConversion2MethodMapper.Source source = new Issue2122TypeConversion2MethodMapper.Source(); + source.setValue( 5 ); + + Issue2122TypeConversion2MethodMapper.Target target = + Issue2122TypeConversion2MethodMapper.INSTANCE.toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getStrings() ).isNotNull(); + assertThat( target.getStrings() ).hasSize( 1 ) + .element( 0 ) + .isEqualTo( "5" ); + } + + @ProcessorTest + @WithClasses( Issue2122Method2TypeConversionMapper.class ) + public void shouldMapMethod2TypeConversion() { + Issue2122Method2TypeConversionMapper.Source source = new Issue2122Method2TypeConversionMapper.Source(); + source.setStrings( Collections.singletonList( "5" ) ); + + Issue2122Method2TypeConversionMapper.Target target = + Issue2122Method2TypeConversionMapper.INSTANCE.toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( 5 ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122TypeConversion2MethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122TypeConversion2MethodMapper.java new file mode 100644 index 0000000000..2555f53dee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2122/Issue2122TypeConversion2MethodMapper.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.ap.test.bugs._2122; + +import java.util.Collections; +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface Issue2122TypeConversion2MethodMapper { + + Issue2122TypeConversion2MethodMapper INSTANCE = Mappers.getMapper( Issue2122TypeConversion2MethodMapper.class ); + + @Mapping(target = "strings", source = "value") + Target toTarget(Source source); + + default List singleEntry(T entry) { + return Collections.singletonList( entry ); + } + + class Source { + Integer value; + + public Integer getValue() { + return value; + } + + public void setValue(Integer value) { + this.value = value; + } + } + + class Target { + List strings; + + public List getStrings() { + return strings; + } + + public void setStrings(List strings) { + this.strings = strings; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/CommitComment.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/CommitComment.java new file mode 100644 index 0000000000..9067f82c3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/CommitComment.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.ap.test.bugs._2124; + +/** + * @author Filip Hrisafov + */ +public class CommitComment { + + private final Integer issueId; + + public CommitComment(Integer issueId) { + this.issueId = issueId; + } + + public Integer getIssueId() { + return issueId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/Issue2124Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/Issue2124Mapper.java new file mode 100644 index 0000000000..af6250c7f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/Issue2124Mapper.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.ap.test.bugs._2124; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2124Mapper { + + Issue2124Mapper INSTANCE = Mappers.getMapper( Issue2124Mapper.class ); + + @Mapping(target = "issueId", source = "comment.issueId", qualifiedByName = "mapped") + CommitComment clone(CommitComment comment, String otherSource); + + @Named("mapped") + default Integer mapIssueNumber(int issueNumber) { + return issueNumber * 2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/Issue2124Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/Issue2124Test.java new file mode 100644 index 0000000000..f2f9873fdb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2124/Issue2124Test.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.test.bugs._2124; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2124") +@WithClasses({ + CommitComment.class, + Issue2124Mapper.class +}) +public class Issue2124Test { + + @ProcessorTest + public void shouldCompile() { + + CommitComment clone = Issue2124Mapper.INSTANCE.clone( new CommitComment( 100 ), null ); + assertThat( clone ).isNotNull(); + assertThat( clone.getIssueId() ).isEqualTo( 200 ); + + clone = Issue2124Mapper.INSTANCE.clone( new CommitComment( null ), null ); + assertThat( clone ).isNotNull(); + assertThat( clone.getIssueId() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Comment.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Comment.java new file mode 100644 index 0000000000..13fe680bb0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Comment.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.ap.test.bugs._2125; + +/** + * @author Filip Hrisafov + */ +public class Comment { + private final Integer issueId; + private final String comment; + + public Comment(Integer issueId, String comment) { + this.issueId = issueId; + this.comment = comment; + } + + public Integer getIssueId() { + return issueId; + } + + public String getComment() { + return comment; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125ErroneousMapper.java new file mode 100644 index 0000000000..bdca172003 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125ErroneousMapper.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.ap.test.bugs._2125; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +@Mapper +public interface Issue2125ErroneousMapper { + + @Mapping(target = "issueId", qualifiedByName = "mapIssueNumber") + @Mapping( target = "comment", ignore = true) + Comment clone(Repository repository); + + @Mapping(target = "issueId", qualifiedByName = "mapIssueNumber") + @Mapping( target = "comment", ignore = true) + Comment clone(Comment comment, Repository repository); + + @Named("mapIssueNumber") + default Integer mapIssueNumber(Integer issueNumber) { + return issueNumber != null ? issueNumber + 1 : null; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125Mapper.java new file mode 100644 index 0000000000..83cf0e1b28 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125Mapper.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.test.bugs._2125; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2125Mapper { + + Issue2125Mapper INSTANCE = Mappers.getMapper( Issue2125Mapper.class ); + + // In this case the issueId from the Comment is used + Comment clone(Comment comment, Integer issueId); + + // When source is not defined then we will use the issueId from the Comment, + // same as when there was no mapping + @Mapping(target = "issueId", qualifiedByName = "mapIssueNumber") + Comment cloneWithQualifier(Comment comment, Integer issueId); + + // When source is defined then we will source the parameter name + @Mapping(target = "issueId", source = "issueId", qualifiedByName = "mapIssueNumber") + Comment cloneWithQualifierExplicitSource(Comment comment, Integer issueId); + + @Named("mapIssueNumber") + default Integer mapIssueNumber(Integer issueNumber) { + return issueNumber != null ? issueNumber + 1 : null; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125Test.java new file mode 100644 index 0000000000..69a564edf0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Issue2125Test.java @@ -0,0 +1,70 @@ +/* + * 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.test.bugs._2125; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2125") +@WithClasses({ + Comment.class, + Repository.class, +}) +public class Issue2125Test { + + @ProcessorTest + @WithClasses({ + Issue2125Mapper.class + }) + public void shouldSelectProperMethod() { + + Comment comment = Issue2125Mapper.INSTANCE.clone( + new Comment( 2125, "Fix issue" ), + 1000 + ); + + assertThat( comment ).isNotNull(); + assertThat( comment.getIssueId() ).isEqualTo( 2125 ); + + comment = Issue2125Mapper.INSTANCE.cloneWithQualifier( + new Comment( 2125, "Fix issue" ), + 1000 + ); + + assertThat( comment ).isNotNull(); + assertThat( comment.getIssueId() ).isEqualTo( 2126 ); + + comment = Issue2125Mapper.INSTANCE.cloneWithQualifierExplicitSource( + new Comment( 2125, "Fix issue" ), + 1000 + ); + + assertThat( comment ).isNotNull(); + assertThat( comment.getIssueId() ).isEqualTo( 1001 ); + } + + @ProcessorTest + @WithClasses({ + Issue2125ErroneousMapper.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = Issue2125ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 15, + alternativeLine = 17, // For some reason javac reports the error on the method instead of the annotation + message = "The type of parameter \"repository\" has no property named \"issueId\". Please define the " + + "source property explicitly."), + }) + public void shouldReportErrorWhenMultipleSourcesMatch() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Repository.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Repository.java new file mode 100644 index 0000000000..bf0c80984f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2125/Repository.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.ap.test.bugs._2125; + +/** + * @author Filip Hrisafov + */ +public class Repository { + + private final String owner; + private final String name; + + public Repository(String owner, String name) { + this.owner = owner; + this.name = name; + } + + public String getOwner() { + return owner; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2131/Issue2131Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2131/Issue2131Mapper.java new file mode 100644 index 0000000000..a0e0b1df7f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2131/Issue2131Mapper.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.ap.test.bugs._2131; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +public interface Issue2131Mapper { + + Issue2131Mapper INSTANCE = Mappers.getMapper( Issue2131Mapper.class ); + + TestDto map(TestModel source); + + class TestModel { + private final String name; + + public TestModel(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class TestDto { + private String name; + + public TestDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2131/Issue2131Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2131/Issue2131Test.java new file mode 100644 index 0000000000..6f7a3ec8a3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2131/Issue2131Test.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.test.bugs._2131; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2131") +@WithClasses(Issue2131Mapper.class) +public class Issue2131Test { + + @ProcessorTest + public void shouldCompile() { + Issue2131Mapper mapper = Issue2131Mapper.INSTANCE; + + Issue2131Mapper.TestDto target = mapper.map( new Issue2131Mapper.TestModel( "test" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isEqualTo( "test" ); + + target = mapper.map( new Issue2131Mapper.TestModel( null ) ); + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2133/Issue2133Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2133/Issue2133Mapper.java new file mode 100644 index 0000000000..484620c0c4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2133/Issue2133Mapper.java @@ -0,0 +1,75 @@ +/* + * 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.test.bugs._2133; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2133Mapper { + + Issue2133Mapper INSTANCE = Mappers.getMapper( Issue2133Mapper.class ); + + @BeanMapping(resultType = Target.class) + AbstractTarget map(Source source); + + class Source { + + private EmbeddedDto embedded; + + public EmbeddedDto getEmbedded() { + return embedded; + } + + public void setEmbedded(EmbeddedDto embedded) { + this.embedded = embedded; + } + } + + class Target extends AbstractTarget { + } + + abstract class AbstractTarget { + + private EmbeddedEntity embedded; + + public EmbeddedEntity getEmbedded() { + return embedded; + } + + public void setEmbedded(EmbeddedEntity embedded) { + this.embedded = embedded; + } + } + + class EmbeddedDto { + + private String s1; + + public String getS1() { + return s1; + } + + public void setS1(String s1) { + this.s1 = s1; + } + } + + class EmbeddedEntity { + + private String s1; + + public String getS1() { + return s1; + } + + public void setS1(String s1) { + this.s1 = s1; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2133/Issue2133Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2133/Issue2133Test.java new file mode 100644 index 0000000000..5f78b3fdde --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2133/Issue2133Test.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.ap.test.bugs._2133; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("2133") +@WithClasses( Issue2133Mapper.class ) +public class Issue2133Test { + + @ProcessorTest + public void shouldCompile() { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2142/Issue2142Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2142/Issue2142Mapper.java new file mode 100644 index 0000000000..198976b37e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2142/Issue2142Mapper.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._2142; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2142Mapper { + + Issue2142Mapper INSTANCE = Mappers.getMapper( Issue2142Mapper.class ); + + _Target map(Source source); + + // CHECKSTYLE:OFF + class _Target { + // CHECKSTYLE:ON + private final String value; + + public _Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2142/Issue2142Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2142/Issue2142Test.java new file mode 100644 index 0000000000..287c39c175 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2142/Issue2142Test.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.ap.test.bugs._2142; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2142") +@WithClasses(Issue2142Mapper.class) +public class Issue2142Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( Issue2142Mapper.class ); + + @ProcessorTest + public void underscorePrefixShouldBeStrippedFromGeneratedLocalVariables() { + Issue2142Mapper._Target target = Issue2142Mapper.INSTANCE.map( new Issue2142Mapper.Source( "value1" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "value1" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Mapper.java new file mode 100644 index 0000000000..f639f4e0dc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Mapper.java @@ -0,0 +1,85 @@ +/* + * 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.test.bugs._2145; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.annotation.XmlElementDecl; +import javax.xml.namespace.QName; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper( uses = Issue2145Mapper.ObjectFactory.class ) +public interface Issue2145Mapper { + + Issue2145Mapper INSTANCE = Mappers.getMapper( Issue2145Mapper.class ); + + @Mapping(target = "nested", source = "value") + Target map(Source source); + + default Nested map(String in) { + Nested nested = new Nested(); + nested.setValue( in ); + return nested; + } + + class Target { + + private JAXBElement nested; + + public JAXBElement getNested() { + return nested; + } + + public void setNested(JAXBElement nested) { + this.nested = nested; + } + } + + class Nested { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Source { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class ObjectFactory { + + private static final QName Q_NAME = new QName( "http://www.test.com/test", "" ); + private static final QName Q_NAME_NESTED = new QName( "http://www.test.com/test", "nested" ); + + @XmlElementDecl(namespace = "http://www.test.com/test", name = "Nested") + public JAXBElement createNested(Nested value) { + return new JAXBElement<>( Q_NAME, Nested.class, null, value ); + } + + @XmlElementDecl(namespace = "http://www.test.com/test", name = "nested", scope = Nested.class) + public JAXBElement createNestedInNestedTarget(Nested value) { + return new JAXBElement<>( Q_NAME_NESTED, Nested.class, Target.class, value ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Test.java new file mode 100644 index 0000000000..38aed76003 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2145/Issue2145Test.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.ap.test.bugs._2145; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2145") +@WithClasses(Issue2145Mapper.class) +@WithJavaxJaxb +public class Issue2145Test { + + @ProcessorTest + public void test() { + Issue2145Mapper.Source source = new Issue2145Mapper.Source(); + source.setValue( "test" ); + + Issue2145Mapper.Target target = Issue2145Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNested() ).isNotNull(); + assertThat( target.getNested().getScope() ).isEqualTo( Issue2145Mapper.Target.class ); + assertThat( target.getNested().getValue() ).isNotNull(); + assertThat( target.getNested().getValue().getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Erroneous2149Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Erroneous2149Mapper.java new file mode 100644 index 0000000000..2b8d73cec2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Erroneous2149Mapper.java @@ -0,0 +1,84 @@ +/* + * 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.test.bugs._2149; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Erroneous2149Mapper { + + @BeanMapping(ignoreByDefault = true) + @Mapping(target = ".", source = "name") + Target map(Source source); + + class Target { + + private String firstName; + private String age; + private String address; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getAge() { + return age; + } + + public void setAge(String age) { + this.age = age; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + class Source { + + private final String age; + private final Name name; + + public Source(String age, Name name) { + this.age = age; + this.name = name; + } + + public String getAge() { + return age; + } + + public Name getName() { + return name; + } + } + + class Name { + + private final String firstName; + + public Name(String firstName) { + this.firstName = firstName; + } + + public String getFirstName() { + return firstName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.java new file mode 100644 index 0000000000..39c415f340 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2149/Issue2149Test.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.test.bugs._2149; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2149") +@WithClasses({ + Erroneous2149Mapper.class +}) +public class Issue2149Test { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = Erroneous2149Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 18, + message = "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not " + + "allowed. You'll need to explicitly ignore the target properties that should be ignored instead." + ) + } + ) + public void shouldGiveCompileErrorWhenBeanMappingIgnoreByDefaultIsCombinedWithMappingTargetThis() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2164/Issue2164Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2164/Issue2164Mapper.java new file mode 100644 index 0000000000..7d152e88c1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2164/Issue2164Mapper.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._2164; + +import java.math.BigDecimal; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2164Mapper { + + Issue2164Mapper INSTANCE = Mappers.getMapper( Issue2164Mapper.class ); + + @Mapping(target = "value", qualifiedByName = "truncate2") + Target map(BigDecimal value); + + @Named( "truncate2" ) + default String truncate2(String in) { + return in.substring( 0, 2 ); + } + + class Target { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2164/Issue2164Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2164/Issue2164Test.java new file mode 100644 index 0000000000..5f7dc7f57f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2164/Issue2164Test.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.ap.test.bugs._2164; + +import java.math.BigDecimal; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2164") +@WithClasses(Issue2164Mapper.class) +public class Issue2164Test { + + @ProcessorTest + public void shouldSelectProperMethod() { + + Issue2164Mapper.Target target = Issue2164Mapper.INSTANCE.map( new BigDecimal( "1234" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "12" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/Issue2170Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/Issue2170Test.java new file mode 100644 index 0000000000..b5c10d120a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/Issue2170Test.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.ap.test.bugs._2170; + +import java.util.Collections; + +import org.mapstruct.ap.test.bugs._2170.dto.AddressDto; +import org.mapstruct.ap.test.bugs._2170.dto.PersonDto; +import org.mapstruct.ap.test.bugs._2170.entity.Address; +import org.mapstruct.ap.test.bugs._2170.entity.Person; +import org.mapstruct.ap.test.bugs._2170.mapper.AddressMapper; +import org.mapstruct.ap.test.bugs._2170.mapper.EntityMapper; +import org.mapstruct.ap.test.bugs._2170.mapper.PersonMapper; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2170") +@WithClasses({ + Address.class, + Person.class, + AddressDto.class, + PersonDto.class, + AddressMapper.class, + PersonMapper.class, + EntityMapper.class, +}) +public class Issue2170Test { + + @ProcessorTest + public void shouldGenerateCodeThatCompiles() { + + AddressDto addressDto = AddressMapper.INSTANCE.toDto( new Address( + "10000", + Collections.singletonList( new Person( "Tester" ) ) + ) ); + + assertThat( addressDto ).isNotNull(); + assertThat( addressDto.getZipCode() ).isEqualTo( "10000" ); + assertThat( addressDto.getPeople() ) + .extracting( PersonDto::getName ) + .containsExactly( "Tester" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/dto/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/dto/AddressDto.java new file mode 100644 index 0000000000..c0b9b06317 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/dto/AddressDto.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.ap.test.bugs._2170.dto; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class AddressDto { + + private final String zipCode; + private final List people; + + public AddressDto(String zipCode, + List people) { + this.zipCode = zipCode; + this.people = people; + } + + public String getZipCode() { + return zipCode; + } + + public List getPeople() { + return people; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/dto/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/dto/PersonDto.java new file mode 100644 index 0000000000..7e6d22e72a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/dto/PersonDto.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.ap.test.bugs._2170.dto; + +/** + * @author Filip Hrisafov + */ +public +class PersonDto { + + private final String name; + + public PersonDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/entity/Address.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/entity/Address.java new file mode 100644 index 0000000000..660ff0a260 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/entity/Address.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.ap.test.bugs._2170.entity; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Address { + private final String zipCode; + private final List people; + + public Address(String zipCode, List people) { + this.zipCode = zipCode; + this.people = people; + } + + public String getZipCode() { + return zipCode; + } + + public List getPeople() { + return people; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/entity/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/entity/Person.java new file mode 100644 index 0000000000..dc36699c5b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/entity/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.ap.test.bugs._2170.entity; + +/** + * @author Filip Hrisafov + */ +public class Person { + private final String name; + + public Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/AddressMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/AddressMapper.java new file mode 100644 index 0000000000..e31937609e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/AddressMapper.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.ap.test.bugs._2170.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2170.dto.AddressDto; +import org.mapstruct.ap.test.bugs._2170.entity.Address; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +//CHECKSTYLE:OFF +@Mapper(uses = { PersonMapper.class }) +//CHECKSTYLE:ON +public interface AddressMapper extends EntityMapper { + + AddressMapper INSTANCE = Mappers.getMapper( AddressMapper.class ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/EntityMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/EntityMapper.java new file mode 100644 index 0000000000..0f24b05483 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/EntityMapper.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.ap.test.bugs._2170.mapper; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public interface EntityMapper { + E toEntity(D dto); + + D toDto(E entity); + + List toEntity(List dtoList); + + List toDto(List entityList); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/PersonMapper.java new file mode 100644 index 0000000000..28e7d0a31e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2170/mapper/PersonMapper.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.ap.test.bugs._2170.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2170.dto.PersonDto; +import org.mapstruct.ap.test.bugs._2170.entity.Person; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface PersonMapper extends EntityMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2174/Issue2174Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2174/Issue2174Test.java new file mode 100644 index 0000000000..b6ec1f08d2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2174/Issue2174Test.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.ap.test.bugs._2174; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2174") +@WithClasses(UserMapper.class) +public class Issue2174Test { + + @ProcessorTest + public void shouldNotWrapCheckedException() throws Exception { + + UserMapper.User user = UserMapper.INSTANCE.map( new UserMapper.UserDto( "Test City", "10000" ) ); + + assertThat( user ).isNotNull(); + assertThat( user.getAddress() ).isNotNull(); + assertThat( user.getAddress().getCity() ).isNotNull(); + assertThat( user.getAddress().getCity().getName() ).isEqualTo( "Test City" ); + assertThat( user.getAddress().getCode() ).isNotNull(); + assertThat( user.getAddress().getCode().getCode() ).isEqualTo( "10000" ); + + assertThatThrownBy( () -> UserMapper.INSTANCE.map( new UserMapper.UserDto( "Zurich", "10000" ) ) ) + .isInstanceOf( UserMapper.CityNotFoundException.class ) + .hasMessage( "City with name 'Zurich' does not exist" ); + + assertThatThrownBy( () -> UserMapper.INSTANCE.map( new UserMapper.UserDto( "Test City", "1000" ) ) ) + .isInstanceOf( UserMapper.PostalCodeNotFoundException.class ) + .hasMessage( "Postal code '1000' does not exist" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2174/UserMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2174/UserMapper.java new file mode 100644 index 0000000000..025201b30c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2174/UserMapper.java @@ -0,0 +1,128 @@ +/* + * 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.test.bugs._2174; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface UserMapper { + + UserMapper INSTANCE = Mappers.getMapper( UserMapper.class ); + + @Mapping(target = "address.city", source = "city") + @Mapping(target = "address.code", source = "postalCode") + User map(UserDto dto) throws CityNotFoundException, PostalCodeNotFoundException; + + default City mapCity(String city) throws CityNotFoundException { + if ( "Test City".equals( city ) ) { + return new City( city ); + } + + throw new CityNotFoundException( "City with name '" + city + "' does not exist" ); + } + + default PostalCode mapCode(String code) throws PostalCodeNotFoundException { + if ( "10000".equals( code ) ) { + return new PostalCode( code ); + } + + throw new PostalCodeNotFoundException( "Postal code '" + code + "' does not exist" ); + } + + class UserDto { + private final String city; + private final String postalCode; + + public UserDto(String city, String postalCode) { + this.city = city; + this.postalCode = postalCode; + } + + public String getCity() { + return city; + } + + public String getPostalCode() { + return postalCode; + } + } + + class User { + + private final Address address; + + public User(Address address) { + this.address = address; + } + + public Address getAddress() { + return address; + } + } + + class Address { + private final City city; + private final PostalCode code; + + public Address(City city, PostalCode code) { + this.city = city; + this.code = code; + } + + public City getCity() { + return city; + } + + public PostalCode getCode() { + return code; + } + } + + class City { + + private final String name; + + public City(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class PostalCode { + + private final String code; + + public PostalCode(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + } + + class CityNotFoundException extends Exception { + + public CityNotFoundException(String message) { + super( message ); + } + } + + class PostalCodeNotFoundException extends Exception { + + public PostalCodeNotFoundException(String message) { + super( message ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2177/Issue2177Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2177/Issue2177Mapper.java new file mode 100644 index 0000000000..bf96c6b5b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2177/Issue2177Mapper.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.ap.test.bugs._2177; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2177Mapper { + + Issue2177Mapper INSTANCE = Mappers.getMapper( Issue2177Mapper.class ); + + Target map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + private final T value; + + public Target(T value) { + this.value = value; + } + + public T getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2177/Issue2177Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2177/Issue2177Test.java new file mode 100644 index 0000000000..5c1c16b27f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2177/Issue2177Test.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.ap.test.bugs._2177; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2177") +@WithClasses({ + Issue2177Mapper.class +}) +public class Issue2177Test { + + @ProcessorTest + public void shouldCorrectlyUseGenericClassesWithConstructorMapping() { + + Issue2177Mapper.Target target = Issue2177Mapper.INSTANCE.map( new Issue2177Mapper.Source( "test" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2185/Issue2185Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2185/Issue2185Test.java new file mode 100644 index 0000000000..7280d26e31 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2185/Issue2185Test.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.ap.test.bugs._2185; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2185") +@WithClasses({ + TodoMapper.class +}) +public class Issue2185Test { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = TodoMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 14, + message = "The mapper org.mapstruct.ap.test.bugs._2185.TodoMapper is referenced itself in Mapper#uses.") + } + ) + public void shouldCompile() { + + TodoMapper.TodoResponse response = TodoMapper.INSTANCE.toResponse( new TodoMapper.TodoEntity( "test" ) ); + + assertThat( response ).isNotNull(); + assertThat( response.getNote() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2185/TodoMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2185/TodoMapper.java new file mode 100644 index 0000000000..2d52bed1fc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2185/TodoMapper.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._2185; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = TodoMapper.class) +public interface TodoMapper { + + TodoMapper INSTANCE = Mappers.getMapper( TodoMapper.class ); + + TodoResponse toResponse(TodoEntity entity); + + class TodoResponse { + + private final String note; + + public TodoResponse(String note) { + this.note = note; + } + + public String getNote() { + return note; + } + } + + class TodoEntity { + + private final String note; + + public TodoEntity(String note) { + this.note = note; + } + + public String getNote() { + return note; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/Issue2195Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/Issue2195Mapper.java new file mode 100644 index 0000000000..cde09dd2e3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/Issue2195Mapper.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.ap.test.bugs._2195; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2195.dto.Source; +import org.mapstruct.ap.test.bugs._2195.dto.Target; +import org.mapstruct.ap.test.bugs._2195.dto.TargetBase; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2195Mapper { + + Issue2195Mapper INSTANCE = Mappers.getMapper( Issue2195Mapper.class ); + + @BeanMapping( resultType = Target.class ) + TargetBase map(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/Issue2195Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/Issue2195Test.java new file mode 100644 index 0000000000..5ee3c35d86 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/Issue2195Test.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._2195; + +import org.mapstruct.ap.test.bugs._2195.dto.Source; +import org.mapstruct.ap.test.bugs._2195.dto.Target; +import org.mapstruct.ap.test.bugs._2195.dto.TargetBase; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2195") +@WithClasses( { Source.class, Target.class, TargetBase.class } ) +public class Issue2195Test { + + @ProcessorTest + @WithClasses( Issue2195Mapper.class ) + public void test() { + + Source source = new Source(); + source.setName( "JohnDoe" ); + + TargetBase target = Issue2195Mapper.INSTANCE.map( source ); + + assertThat( target ).isInstanceOf( Target.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/Source.java new file mode 100644 index 0000000000..78a4bba1a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/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.ap.test.bugs._2195.dto; + +public class Source { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/Target.java new file mode 100644 index 0000000000..8a74d44104 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/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.ap.test.bugs._2195.dto; + +public class Target extends TargetBase { + + protected Target(Builder builder) { + super( builder ); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder extends TargetBase.Builder { + + protected Builder() { + } + + public Target build() { + return new Target( this ); + } + + public Builder name(String name) { + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/TargetBase.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/TargetBase.java new file mode 100644 index 0000000000..ffb2eff2bc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2195/dto/TargetBase.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._2195.dto; + +public class TargetBase { + + private final String name; + + protected TargetBase(Builder builder) { + this.name = builder.name; + } + + public static Builder builder() { + return new Builder(); + } + + public String getName() { + return name; + } + + public static class Builder { + + protected Builder() { + } + + private String name; + + public TargetBase build() { + return new TargetBase( this ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2197/Issue2197Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2197/Issue2197Mapper.java new file mode 100644 index 0000000000..def6f8f601 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2197/Issue2197Mapper.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._2197; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2197Mapper { + + Issue2197Mapper INSTANCE = Mappers.getMapper( Issue2197Mapper.class ); + + _0Target map(Source source); + + // CHECKSTYLE:OFF + class _0Target { + // CHECKSTYLE:ON + private final String value; + + public _0Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2197/Issue2197Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2197/Issue2197Test.java new file mode 100644 index 0000000000..774d1f396f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2197/Issue2197Test.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.ap.test.bugs._2197; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2197") +@WithClasses(Issue2197Mapper.class) +public class Issue2197Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( Issue2197Mapper.class ); + + @ProcessorTest + public void underscoreAndDigitPrefixShouldBeStrippedFromGeneratedLocalVariables() { + Issue2197Mapper._0Target target = Issue2197Mapper.INSTANCE.map( new Issue2197Mapper.Source( "value1" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "value1" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Car.java new file mode 100755 index 0000000000..4f2ec92af6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Car.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.ap.test.bugs._2213; + +public class Car { + private int[] intData; + private Long[] longData; + + public int[] getIntData() { + return intData; + } + + public void setIntData(int[] intData) { + this.intData = intData; + } + + public Long[] getLongData() { + return longData; + } + + public void setLongData(Long[] longData) { + this.longData = longData; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Car2.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Car2.java new file mode 100755 index 0000000000..ec0cd6de6d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Car2.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.ap.test.bugs._2213; + +public class Car2 { + private int[] intData; + private Long[] longData; + + public @NotNull int[] getIntData() { + return intData; + } + + public void setIntData(int[] intData) { + this.intData = intData; + } + + public @NotNull Long[] getLongData() { + return longData; + } + + public void setLongData(Long[] longData) { + this.longData = longData; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/CarMapper.java new file mode 100644 index 0000000000..f3b25c0213 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/CarMapper.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.ap.test.bugs._2213; + +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = DeepClone.class) +public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + Car toCar(Car2 car2); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Issue2213Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Issue2213Test.java new file mode 100644 index 0000000000..2f8f951668 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/Issue2213Test.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.ap.test.bugs._2213; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + NotNull.class, + CarMapper.class, + Car.class, + Car2.class +}) +@IssueKey("2213") +public class Issue2213Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( CarMapper.class ); + + @ProcessorTest + public void testShouldNotGenerateIntermediatePrimitiveMappingMethod() { + Car2 car = new Car2(); + int[] sourceInt = { 1, 2, 3 }; + car.setIntData( sourceInt ); + Long[] sourceLong = { 1L, 2L, 3L }; + car.setLongData( sourceLong ); + Car target = CarMapper.INSTANCE.toCar( car ); + + assertThat( target ).isNotNull(); + assertThat( target.getIntData() ) + .containsExactly( 1, 2, 3 ) + .isNotSameAs( sourceInt ); + assertThat( target.getLongData() ) + .containsExactly( 1L, 2L, 3L ) + .isNotSameAs( sourceLong ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/NotNull.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/NotNull.java new file mode 100644 index 0000000000..3b45f03ed4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2213/NotNull.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.ap.test.bugs._2213; + +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; + +@Target({ + ElementType.METHOD, + ElementType.TYPE_USE +}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface NotNull { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/Issue2221Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/Issue2221Test.java new file mode 100644 index 0000000000..ef7f2f4ba6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/Issue2221Test.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._2221; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2221") +@WithClasses({ + RestConfig.class, + RestSiteDto.class, + RestSiteMapper.class, + SiteDto.class, +}) +public class Issue2221Test { + + @ProcessorTest + public void multiSourceInheritConfigurationShouldWork() { + SiteDto site = RestSiteMapper.INSTANCE.convert( + new RestSiteDto( "restTenant", "restSite", "restCti" ), + "parameterTenant", + "parameterSite" + ); + + assertThat( site ).isNotNull(); + assertThat( site.getTenantId() ).isEqualTo( "parameterTenant" ); + assertThat( site.getSiteId() ).isEqualTo( "parameterSite" ); + assertThat( site.getCtiId() ).isEqualTo( "restCti" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestConfig.java new file mode 100644 index 0000000000..9a0f79f82d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestConfig.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.ap.test.bugs._2221; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; + +/** + * @author Filip Hrisafov + */ +@MapperConfig +public interface RestConfig { + + @Mapping(target = "tenantId", source = "tenantId") + @Mapping(target = "siteId", source = "siteId") + @Mapping(target = "ctiId", source = "source.cti", defaultValue = "unknown") + SiteDto convert(RestSiteDto source, String tenantId, String siteId); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestSiteDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestSiteDto.java new file mode 100644 index 0000000000..eb1a56b344 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestSiteDto.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.test.bugs._2221; + +/** + * @author Filip Hrisafov + */ +public class RestSiteDto { + + private final String tenantId; + private final String siteId; + private final String cti; + + public RestSiteDto(String tenantId, String siteId, String cti) { + this.tenantId = tenantId; + this.siteId = siteId; + this.cti = cti; + } + + public String getTenantId() { + return tenantId; + } + + public String getSiteId() { + return siteId; + } + + public String getCti() { + return cti; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestSiteMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestSiteMapper.java new file mode 100644 index 0000000000..cf7281f44f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/RestSiteMapper.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.ap.test.bugs._2221; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = RestConfig.class) +public interface RestSiteMapper { + + RestSiteMapper INSTANCE = Mappers.getMapper( RestSiteMapper.class ); + + @InheritConfiguration + SiteDto convert(RestSiteDto source, String tenantId, String siteId); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/SiteDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/SiteDto.java new file mode 100644 index 0000000000..7c80410029 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2221/SiteDto.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.test.bugs._2221; + +/** + * @author Filip Hrisafov + */ +public class SiteDto { + + private final String tenantId; + private final String siteId; + private final String ctiId; + + public SiteDto(String tenantId, String siteId, String ctiId) { + this.tenantId = tenantId; + this.siteId = siteId; + this.ctiId = ctiId; + } + + public String getTenantId() { + return tenantId; + } + + public String getSiteId() { + return siteId; + } + + public String getCtiId() { + return ctiId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/Issue2233Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/Issue2233Test.java new file mode 100644 index 0000000000..66911d936d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/Issue2233Test.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._2233; + +import java.util.Collections; +import java.util.Optional; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2233") +@WithClasses( { + Program.class, + ProgramAggregate.class, + ProgramDto.class, + ProgramMapper.class, + ProgramResponseDto.class, +} ) +public class Issue2233Test { + + @ProcessorTest + public void shouldCorrectlyMapFromOptionalToCollection() { + ProgramResponseDto response = ProgramMapper.INSTANCE.map( new ProgramAggregate( Collections.singleton( + new Program( + "Optional Mapping", + "123" + ) ) ) ); + + assertThat( response ).isNotNull(); + assertThat( response.getPrograms() ).isPresent(); + assertThat( response.getPrograms().get() ) + .extracting( ProgramDto::getName, ProgramDto::getNumber ) + .containsExactly( + tuple( Optional.of( "Optional Mapping" ), Optional.of( "123" ) ) + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/Program.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/Program.java new file mode 100644 index 0000000000..748ad4901c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/Program.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.test.bugs._2233; + +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class Program { + + private final String name; + private final String number; + + public Program(String name, String number) { + this.name = name; + this.number = number; + } + + public Optional getName() { + return Optional.ofNullable( name ); + } + + public Optional getNumber() { + return Optional.ofNullable( number ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramAggregate.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramAggregate.java new file mode 100644 index 0000000000..c3fdabe151 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramAggregate.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.ap.test.bugs._2233; + +import java.util.Collection; +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class ProgramAggregate { + + private final Collection programs; + + public ProgramAggregate(Collection programs) { + this.programs = programs; + } + + public Optional> getPrograms() { + return Optional.ofNullable( programs ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramDto.java new file mode 100644 index 0000000000..d567998667 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramDto.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.test.bugs._2233; + +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class ProgramDto { + + private final String name; + private final String number; + + public ProgramDto(String name, String number) { + this.name = name; + this.number = number; + } + + public Optional getName() { + return Optional.ofNullable( name ); + } + + public Optional getNumber() { + return Optional.ofNullable( number ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramMapper.java new file mode 100644 index 0000000000..e2737f8e3b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramMapper.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.ap.test.bugs._2233; + +import java.util.Collection; +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ProgramMapper { + + ProgramMapper INSTANCE = Mappers.getMapper( ProgramMapper.class ); + + ProgramResponseDto map(ProgramAggregate programAggregate); + + ProgramDto map(Program sourceProgramDto); + + Collection mapPrograms(Collection sourcePrograms); + + default T fromOptional(Optional optional) { + return optional.orElse( null ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramResponseDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramResponseDto.java new file mode 100644 index 0000000000..61e7df4d00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2233/ProgramResponseDto.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.ap.test.bugs._2233; + +import java.util.Collection; +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class ProgramResponseDto { + + private final Collection programs; + + public ProgramResponseDto(Collection programs) { + this.programs = programs; + } + + public Optional> getPrograms() { + return Optional.ofNullable( programs ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Car.java new file mode 100644 index 0000000000..70bb9a413c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Car.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.ap.test.bugs._2236; + +/** + * @author Filip Hrisafov + */ +public class Car { + + private String name; + private String type; + private Owner ownerA; + private Owner ownerB; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Owner getOwnerA() { + return ownerA; + } + + public void setOwnerA(Owner ownerA) { + this.ownerA = ownerA; + } + + public Owner getOwnerB() { + return ownerB; + } + + public void setOwnerB(Owner ownerB) { + this.ownerB = ownerB; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/CarDto.java new file mode 100644 index 0000000000..909bb3649b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/CarDto.java @@ -0,0 +1,58 @@ +/* + * 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.test.bugs._2236; + +/** + * @author Filip Hrisafov + */ +public class CarDto { + + private String name; + private String ownerNameA; + private String ownerNameB; + private String ownerCityA; + private String ownerCityB; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getOwnerNameA() { + return ownerNameA; + } + + public void setOwnerNameA(String ownerNameA) { + this.ownerNameA = ownerNameA; + } + + public String getOwnerNameB() { + return ownerNameB; + } + + public void setOwnerNameB(String ownerNameB) { + this.ownerNameB = ownerNameB; + } + + public String getOwnerCityA() { + return ownerCityA; + } + + public void setOwnerCityA(String ownerCityA) { + this.ownerCityA = ownerCityA; + } + + public String getOwnerCityB() { + return ownerCityB; + } + + public void setOwnerCityB(String ownerCityB) { + this.ownerCityB = ownerCityB; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/CarMapper.java new file mode 100644 index 0000000000..d3366ea2a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/CarMapper.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.ap.test.bugs._2236; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + @Mapping(target = "ownerA.name", source = "carDto.ownerNameA") + @Mapping(target = "ownerA.city", source = "carDto.ownerCityA") + @Mapping(target = "ownerB.name", source = "carDto.ownerNameB") + @Mapping(target = "ownerB.city", source = "carDto.ownerCityB") + @Mapping(target = "name", source = "carDto.name") + @Mapping(target = "type", source = "type") + Car vehicleToCar(Vehicle vehicle); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Issue2236Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Issue2236Test.java new file mode 100644 index 0000000000..a1bbea638b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Issue2236Test.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.ap.test.bugs._2236; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2236") +@WithClasses({ + Car.class, + CarDto.class, + CarMapper.class, + Owner.class, + Vehicle.class, +}) +public class Issue2236Test { + + @ProcessorTest + public void shouldCorrectlyMapSameTypesWithDifferentNestedMappings() { + + Vehicle vehicle = new Vehicle(); + vehicle.setType( "Sedan" ); + CarDto carDto = new CarDto(); + vehicle.setCarDto( carDto ); + + carDto.setName( "Private car" ); + carDto.setOwnerNameA( "Owner A" ); + carDto.setOwnerCityA( "Zurich" ); + + carDto.setOwnerNameB( "Owner B" ); + carDto.setOwnerCityB( "Bern" ); + + Car car = CarMapper.INSTANCE.vehicleToCar( vehicle ); + + assertThat( car ).isNotNull(); + assertThat( car.getType() ).isEqualTo( "Sedan" ); + assertThat( car.getName() ).isEqualTo( "Private car" ); + assertThat( car.getOwnerA() ).isNotNull(); + assertThat( car.getOwnerA().getName() ).isEqualTo( "Owner A" ); + assertThat( car.getOwnerA().getCity() ).isEqualTo( "Zurich" ); + assertThat( car.getOwnerB() ).isNotNull(); + assertThat( car.getOwnerB().getName() ).isEqualTo( "Owner B" ); + assertThat( car.getOwnerB().getCity() ).isEqualTo( "Bern" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Owner.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Owner.java new file mode 100644 index 0000000000..5f200f527a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Owner.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.ap.test.bugs._2236; + +/** + * @author Filip Hrisafov + */ +public class Owner { + + private String name; + private String city; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Vehicle.java new file mode 100644 index 0000000000..fc8e744e82 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2236/Vehicle.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.test.bugs._2236; + +/** + * @author Filip Hrisafov + */ +public class Vehicle { + private CarDto carDto; + private String type; + + public CarDto getCarDto() { + return carDto; + } + + public void setCarDto(CarDto carDto) { + this.carDto = carDto; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/Issue2245Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/Issue2245Test.java new file mode 100644 index 0000000000..69af6fef8a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/Issue2245Test.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.ap.test.bugs._2245; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2245") +@WithClasses({ + TestMapper.class +}) +public class Issue2245Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( TestMapper.class ); + + @ProcessorTest + public void shouldGenerateSourceGetMethodOnce() { + + TestMapper.Tenant tenant = + TestMapper.INSTANCE.map( new TestMapper.TenantDTO( new TestMapper.Inner( "acme" ) ) ); + + assertThat( tenant ).isNotNull(); + assertThat( tenant.getId() ).isEqualTo( "acme" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/TestMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/TestMapper.java new file mode 100644 index 0000000000..750ee12fa5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2245/TestMapper.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.test.bugs._2245; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface TestMapper { + + TestMapper INSTANCE = Mappers.getMapper( TestMapper.class ); + + class Tenant { + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } + + class Inner { + private final String id; + + public Inner(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + + class TenantDTO { + private final Inner inner; + + public TenantDTO(Inner inner) { + this.inner = inner; + } + + public Inner getInner() { + return inner; + } + } + + @Mapping(target = "id", source = "inner.id", defaultValue = "test") + Tenant map(TenantDTO tenant); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Mapper.java new file mode 100644 index 0000000000..d09f3440fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Mapper.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.ap.test.bugs._2251; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2251Mapper { + + Issue2251Mapper INSTANCE = Mappers.getMapper( Issue2251Mapper.class ); + + @Mapping(target = "value1", source = "source.value") + Target map(Source source, String value2); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Test.java new file mode 100644 index 0000000000..1c430b00ca --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Issue2251Test.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.test.bugs._2251; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2251") +@WithClasses({ + Issue2251Mapper.class, + Source.class, + Target.class, +}) +public class Issue2251Test { + + @ProcessorTest + public void shouldGenerateCorrectCode() { + + Target target = Issue2251Mapper.INSTANCE.map( new Source( "source" ), "test" ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue1() ).isEqualTo( "source" ); + assertThat( target.getValue2() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Source.java new file mode 100644 index 0000000000..cbd882a8f8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Source.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.ap.test.bugs._2251; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Target.java new file mode 100644 index 0000000000..363129797f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2251/Target.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.ap.test.bugs._2251; + +public class Target { + private final String value1; + private final String value2; + + public Target(String value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public String getValue1() { + return value1; + } + + public String getValue2() { + return value2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/Issue2253Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/Issue2253Test.java new file mode 100644 index 0000000000..ae1c1eea79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/Issue2253Test.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.ap.test.bugs._2253; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2253") +@WithClasses( { + TestMapper.class, +} ) +public class Issue2253Test { + + @ProcessorTest + public void shouldNotTreatMatchedSourceParameterAsBean() { + + TestMapper.Person person = TestMapper.INSTANCE.map( new TestMapper.PersonDto( 20 ), "Tester" ); + + assertThat( person.getAge() ).isEqualTo( 20 ); + assertThat( person.getName() ).isEqualTo( "Tester" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/TestMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/TestMapper.java new file mode 100644 index 0000000000..1e05e444a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2253/TestMapper.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._2253; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface TestMapper { + + TestMapper INSTANCE = Mappers.getMapper( TestMapper.class ); + + Person map(PersonDto personDto, String name); + + class Person { + private final int age; + private final String name; + + public Person(int age, String name) { + this.age = age; + this.name = name; + } + + public int getAge() { + return age; + } + + public String getName() { + return name; + } + } + + class PersonDto { + private final int age; + + public PersonDto(int age) { + this.age = age; + } + + public int getAge() { + return age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Erroneous2263Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Erroneous2263Mapper.java new file mode 100644 index 0000000000..59a0a42ddb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Erroneous2263Mapper.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.ap.test.bugs._2263; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Erroneous2263Mapper { + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Issue2263Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Issue2263Test.java new file mode 100644 index 0000000000..53fd5d88a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Issue2263Test.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.test.bugs._2263; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2263") +@WithClasses({ + Erroneous2263Mapper.class, + Source.class, + Target.class, +}) +public class Issue2263Test { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = Erroneous2263Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 16, + message = "Can't map property \"Object value\" to \"String value\". " + + "Consider to declare/implement a mapping method: \"String map(Object value)\".") + }) + public void shouldCorrectlyReportUnmappableTargetObject() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Source.java new file mode 100644 index 0000000000..54e5b7fd38 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Source.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.ap.test.bugs._2263; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final Object value; + + public Source(Object value) { + this.value = value; + } + + public Object getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Target.java new file mode 100644 index 0000000000..943a51264c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2263/Target.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.ap.test.bugs._2263; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperA.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperA.java new file mode 100644 index 0000000000..8527be3f89 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperA.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.ap.test.bugs._2278; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * ReproducerA + * + */ +@Mapper +public interface Issue2278MapperA { + + Issue2278MapperA INSTANCE = Mappers.getMapper( Issue2278MapperA.class ); + + @Mapping( target = "detailsDTO", source = "details" ) + @Mapping( target = "detailsDTO.fuelType", ignore = true ) + CarDTO map(Car in); + + // checkout the Issue2278ReferenceMapper, the @InheritInverseConfiguration + // is de-facto @Mapping( target = "details", source = "detailsDTO" ) + @InheritInverseConfiguration + @Mapping( target = "details.model", ignore = true ) + @Mapping( target = "details.type", constant = "gto") + @Mapping( target = "details.fuel", source = "detailsDTO.fuelType") + Car map(CarDTO in); + + class Car { + //CHECKSTYLE:OFF + public Details details; + //CHECKSTYLE:ON + } + + class CarDTO { + //CHECKSTYLE:OFF + public DetailsDTO detailsDTO; + //CHECKSTYLE:ON + } + + class Details { + //CHECKSTYLE:OFF + public String brand; + public String model; + public String type; + public String fuel; + //CHECKSTYLE:ON + } + + class DetailsDTO { + //CHECKSTYLE:OFF + public String brand; + public String fuelType; + //CHECKSTYLE:ON + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperB.java new file mode 100644 index 0000000000..b9d0abd523 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278MapperB.java @@ -0,0 +1,65 @@ +/* + * 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.test.bugs._2278; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * ReproducerB + * + */ +@Mapper +public interface Issue2278MapperB { + + Issue2278MapperB INSTANCE = Mappers.getMapper( Issue2278MapperB.class ); + + // id mapping is cros-linked + @Mapping( target = "amount", source = "price" ) + @Mapping( target = "detailsDTO.brand", source = "details.type" ) + @Mapping( target = "detailsDTO.id1", source = "details.id2" ) + @Mapping( target = "detailsDTO.id2", source = "details.id1" ) + CarDTO map(Car in); + + // inherit inverse, but undo cross-link in one sweep + @InheritInverseConfiguration // inherits all + @Mapping( target = "details", source = "detailsDTO" ) // resets everything on details <> detailsDto + @Mapping( target = "details.type", source = "detailsDTO.brand" ) // so this needs to be redone + Car map2(CarDTO in); + + class Car { + //CHECKSTYLE:OFF + public float price; + public Details details; + //CHECKSTYLE:ON + } + + class CarDTO { + //CHECKSTYLE:OFF + public float amount; + public DetailsDTO detailsDTO; + //CHECKSTYLE:ON + } + + class Details { + //CHECKSTYLE:OFF + public String type; + public String id1; + public String id2; + //CHECKSTYLE:ON + } + + class DetailsDTO { + //CHECKSTYLE:OFF + public String brand; + public String id1; + public String id2; + //CHECKSTYLE:ON + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278ReferenceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278ReferenceMapper.java new file mode 100644 index 0000000000..48b457f84c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278ReferenceMapper.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.ap.test.bugs._2278; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * A reference mapper, that checks how a regular forward merge works + */ +@Mapper +public interface Issue2278ReferenceMapper { + + Issue2278ReferenceMapper INSTANCE = Mappers.getMapper( Issue2278ReferenceMapper.class ); + + @Mapping( target = "details", source = "detailsDTO" ) + @Mapping( target = "details.model", ignore = true ) + @Mapping( target = "details.type", constant = "gto") + @Mapping( target = "details.fuel", source = "detailsDTO.fuelType") + Car map(CarDTO in); + + class Car { + //CHECKSTYLE:OFF + public Details details; + //CHECKSTYLE:ON + } + + class CarDTO { + //CHECKSTYLE:OFF + public DetailsDTO detailsDTO; + //CHECKSTYLE:ON + } + + class Details { + //CHECKSTYLE:OFF + public String brand; + public String model; + public String type; + public String fuel; + //CHECKSTYLE:ON + } + + class DetailsDTO { + //CHECKSTYLE:OFF + public String brand; + public String fuelType; + //CHECKSTYLE:ON + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278Test.java new file mode 100644 index 0000000000..7289da4ff1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2278/Issue2278Test.java @@ -0,0 +1,92 @@ +/* + * 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.test.bugs._2278; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2278") +public class Issue2278Test { + + @ProcessorTest + @WithClasses( Issue2278ReferenceMapper.class ) + public void testReferenceMergeBehaviour() { + + Issue2278ReferenceMapper.CarDTO dto = new Issue2278ReferenceMapper.CarDTO(); + dto.detailsDTO = new Issue2278ReferenceMapper.DetailsDTO(); + dto.detailsDTO.brand = "Ford"; + dto.detailsDTO.fuelType = "petrol"; + + Issue2278ReferenceMapper.Car target = Issue2278ReferenceMapper.INSTANCE.map( dto ); + + assertThat( target ).isNotNull(); + assertThat( target.details ).isNotNull(); + assertThat( target.details.brand ).isEqualTo( "Ford" ); + assertThat( target.details.model ).isNull(); + assertThat( target.details.type ).isEqualTo( "gto" ); + assertThat( target.details.fuel ).isEqualTo( "petrol" ); + + } + + @ProcessorTest + @WithClasses( Issue2278MapperA.class ) + public void shouldBehaveJustAsTestReferenceMergeBehaviour() { + + Issue2278MapperA.CarDTO dto = new Issue2278MapperA.CarDTO(); + dto.detailsDTO = new Issue2278MapperA.DetailsDTO(); + dto.detailsDTO.brand = "Ford"; + dto.detailsDTO.fuelType = "petrol"; + + Issue2278MapperA.Car target = Issue2278MapperA.INSTANCE.map( dto ); + + assertThat( target ).isNotNull(); + assertThat( target.details ).isNotNull(); + assertThat( target.details.brand ).isEqualTo( "Ford" ); + assertThat( target.details.model ).isNull(); + assertThat( target.details.type ).isEqualTo( "gto" ); + assertThat( target.details.fuel ).isEqualTo( "petrol" ); + + } + + @ProcessorTest + @WithClasses( Issue2278MapperB.class ) + public void shouldOverrideDetailsMappingWithRedefined() { + + Issue2278MapperB.Car source = new Issue2278MapperB.Car(); + source.details = new Issue2278MapperB.Details(); + source.details.type = "Ford"; + source.details.id1 = "id1"; + source.details.id2 = "id2"; + source.price = 20000f; + + Issue2278MapperB.CarDTO target1 = Issue2278MapperB.INSTANCE.map( source ); + + assertThat( target1 ).isNotNull(); + assertThat( target1.amount ).isEqualTo( 20000f ); + assertThat( target1.detailsDTO ).isNotNull(); + assertThat( target1.detailsDTO.brand ).isEqualTo( "Ford" ); + assertThat( target1.detailsDTO.id1 ).isEqualTo( "id2" ); + assertThat( target1.detailsDTO.id2 ).isEqualTo( "id1" ); + + // restore the mappings, just to make it logical again + target1.detailsDTO.id1 = "id1"; + target1.detailsDTO.id2 = "id2"; + + // now check the reverse inheritance + Issue2278MapperB.Car target2 = Issue2278MapperB.INSTANCE.map2( target1 ); + + assertThat( target2 ).isNotNull(); + assertThat( target2.price ).isEqualTo( 20000f ); + assertThat( target2.details ).isNotNull(); + assertThat( target2.details.type ).isEqualTo( "Ford" ); // should inherit + assertThat( target2.details.id1 ).isEqualTo( "id1" ); // should be undone + assertThat( target2.details.id2 ).isEqualTo( "id2" ); // should be undone + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Artifact.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Artifact.java new file mode 100644 index 0000000000..316959f48b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Artifact.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.ap.test.bugs._2301; + +import java.util.Set; + +/** + * @author Filip Hrisafov + */ +public class Artifact { + + private String name; + private Set dependantBuildRecords; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getDependantBuildRecords() { + return dependantBuildRecords; + } + + public void setDependantBuildRecords(Set dependantBuildRecords) { + this.dependantBuildRecords = dependantBuildRecords; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String name; + private Set dependantBuildRecords; + + public Artifact build() { + Artifact artifact = new Artifact(); + artifact.setName( name ); + artifact.setDependantBuildRecords( dependantBuildRecords ); + + return artifact; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder dependantBuildRecord(String dependantBuildRecord) { + this.dependantBuildRecords.add( dependantBuildRecord ); + return this; + } + + public Builder dependantBuildRecords(Set dependantBuildRecords) { + this.dependantBuildRecords = dependantBuildRecords; + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/ArtifactDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/ArtifactDto.java new file mode 100644 index 0000000000..68b9280930 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/ArtifactDto.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.ap.test.bugs._2301; + +/** + * @author Filip Hrisafov + */ +public class ArtifactDto { + + private final String name; + + public ArtifactDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Mapper.java new file mode 100644 index 0000000000..cd3cca3744 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Mapper.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.ap.test.bugs._2301; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2301Mapper { + + Issue2301Mapper INSTANCE = Mappers.getMapper( Issue2301Mapper.class ); + + @Mapping(target = "dependantBuildRecords", ignore = true) + @Mapping(target = "dependantBuildRecord", ignore = true) + Artifact map(ArtifactDto dto); + + @InheritConfiguration + void update(@MappingTarget Artifact artifact, ArtifactDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Test.java new file mode 100644 index 0000000000..3986ab1066 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2301/Issue2301Test.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.test.bugs._2301; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2301") +@WithClasses({ + Artifact.class, + ArtifactDto.class, + Issue2301Mapper.class +}) +public class Issue2301Test { + + @ProcessorTest + public void shouldCorrectlyIgnoreProperties() { + Artifact artifact = Issue2301Mapper.INSTANCE.map( new ArtifactDto( "mapstruct" ) ); + + assertThat( artifact ).isNotNull(); + assertThat( artifact.getName() ).isEqualTo( "mapstruct" ); + assertThat( artifact.getDependantBuildRecords() ).isNull(); + + Issue2301Mapper.INSTANCE.update( artifact, new ArtifactDto( "mapstruct-processor" ) ); + + assertThat( artifact.getName() ).isEqualTo( "mapstruct-processor" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Mapper.java new file mode 100644 index 0000000000..20621e4d01 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Mapper.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.ap.test.bugs._2318; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(config = Issue2318Mapper.Config.class) +public interface Issue2318Mapper { + + Issue2318Mapper INSTANCE = Mappers.getMapper( Issue2318Mapper.class ); + + @MapperConfig + interface Config { + + @Mapping(target = "parentValue1", source = "holder") + TargetParent mapParent(SourceParent parent); + + @InheritConfiguration(name = "mapParent") + @Mapping(target = "childValue", source = "value") + @Mapping(target = "parentValue2", source = "holder.parentValue2") + TargetChild mapChild(SourceChild child); + } + + @InheritConfiguration(name = "mapChild") + TargetChild mapChild(SourceChild child); + + default String parentValue1(SourceParent.Holder holder) { + return holder.getParentValue1(); + } + + class SourceParent { + private Holder holder; + + public Holder getHolder() { + return holder; + } + + public void setHolder(Holder holder) { + this.holder = holder; + } + + public static class Holder { + private String parentValue1; + private Integer parentValue2; + + public String getParentValue1() { + return parentValue1; + } + + public void setParentValue1(String parentValue1) { + this.parentValue1 = parentValue1; + } + + public Integer getParentValue2() { + return parentValue2; + } + + public void setParentValue2(Integer parentValue2) { + this.parentValue2 = parentValue2; + } + } + } + + class SourceChild extends SourceParent { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class TargetParent { + private String parentValue1; + private Integer parentValue2; + + public String getParentValue1() { + return parentValue1; + } + + public void setParentValue1(String parentValue1) { + this.parentValue1 = parentValue1; + } + + public Integer getParentValue2() { + return parentValue2; + } + + public void setParentValue2(Integer parentValue2) { + this.parentValue2 = parentValue2; + } + } + + class TargetChild extends TargetParent { + private String childValue; + + public String getChildValue() { + return childValue; + } + + public void setChildValue(String childValue) { + this.childValue = childValue; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Test.java new file mode 100644 index 0000000000..a9a06ea470 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2318/Issue2318Test.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.test.bugs._2318; + +import org.mapstruct.ap.test.bugs._2318.Issue2318Mapper.SourceChild; +import org.mapstruct.ap.test.bugs._2318.Issue2318Mapper.TargetChild; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("2318") +public class Issue2318Test { + + @ProcessorTest + @WithClasses( Issue2318Mapper.class ) + public void shouldMap() { + + SourceChild source = new SourceChild(); + source.setValue( "From child" ); + source.setHolder( new Issue2318Mapper.SourceParent.Holder() ); + source.getHolder().setParentValue1( "From parent" ); + source.getHolder().setParentValue2( 12 ); + + TargetChild target = Issue2318Mapper.INSTANCE.mapChild( source ); + + assertThat( target.getParentValue1() ).isEqualTo( "From parent" ); + assertThat( target.getParentValue2() ).isEqualTo( 12 ); + assertThat( target.getChildValue() ).isEqualTo( "From child" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2347/ErroneousClassWithPrivateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2347/ErroneousClassWithPrivateMapper.java new file mode 100644 index 0000000000..c18620887e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2347/ErroneousClassWithPrivateMapper.java @@ -0,0 +1,51 @@ +/* + * 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.test.bugs._2347; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +public class ErroneousClassWithPrivateMapper { + + public static class Target { + private final String id; + + public Target(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + + public static class Source { + + private final String id; + + public Source(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + + @Mapper + private interface PrivateInterfaceMapper { + + Target map(Source source); + } + + @Mapper + private abstract class PrivateClassMapper { + + public abstract Target map(Source source); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2347/Issue2347Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2347/Issue2347Test.java new file mode 100644 index 0000000000..e54816d576 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2347/Issue2347Test.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._2347; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2347") +@WithClasses({ + ErroneousClassWithPrivateMapper.class +}) +public class Issue2347Test { + + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = ErroneousClassWithPrivateMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 41, + message = "Cannot create an implementation for mapper PrivateInterfaceMapper," + + " because it is a private interface." + ), + @Diagnostic( + type = ErroneousClassWithPrivateMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 47, + message = "Cannot create an implementation for mapper PrivateClassMapper," + + " because it is a private class." + ) + } + ) + @ProcessorTest + public void shouldGenerateCompileErrorWhenMapperIsPrivate() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/Issue2352Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/Issue2352Test.java new file mode 100644 index 0000000000..fa9edf0bf4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/Issue2352Test.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._2352; + +import java.util.List; + +import org.mapstruct.ap.test.bugs._2352.dto.TheDto; +import org.mapstruct.ap.test.bugs._2352.dto.TheModel; +import org.mapstruct.ap.test.bugs._2352.dto.TheModels; +import org.mapstruct.ap.test.bugs._2352.mapper.TheModelMapper; +import org.mapstruct.ap.test.bugs._2352.mapper.TheModelsMapper; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2352") +@WithClasses({ + TheDto.class, + TheModel.class, + TheModels.class, + TheModelMapper.class, + TheModelsMapper.class, +}) +public class Issue2352Test { + + @ProcessorTest + public void shouldGenerateValidCode() { + TheModels theModels = new TheModels(); + theModels.add( new TheModel( "1" ) ); + theModels.add( new TheModel( "2" ) ); + + List theDtos = TheModelsMapper.INSTANCE.convert( theModels ); + + assertThat( theDtos ) + .extracting( TheDto::getId ) + .containsExactly( "1", "2" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheDto.java new file mode 100644 index 0000000000..a07c8bd093 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheDto.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.ap.test.bugs._2352.dto; + +/** + * @author Filip Hrisafov + */ +public class TheDto { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheModel.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheModel.java new file mode 100644 index 0000000000..fc7b1e89be --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheModel.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.ap.test.bugs._2352.dto; + +/** + * @author Filip Hrisafov + */ +public class TheModel { + + private final String id; + + public TheModel(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheModels.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheModels.java new file mode 100644 index 0000000000..37359ce779 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/dto/TheModels.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.ap.test.bugs._2352.dto; + +import java.util.ArrayList; + +/** + * @author Filip Hrisafov + */ +public class TheModels extends ArrayList { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/mapper/TheModelMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/mapper/TheModelMapper.java new file mode 100644 index 0000000000..2e9b5be478 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/mapper/TheModelMapper.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.ap.test.bugs._2352.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2352.dto.TheDto; +import org.mapstruct.ap.test.bugs._2352.dto.TheModel; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface TheModelMapper { + + TheDto convert(TheModel theModel); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/mapper/TheModelsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/mapper/TheModelsMapper.java new file mode 100644 index 0000000000..cf6c30409c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2352/mapper/TheModelsMapper.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.ap.test.bugs._2352.mapper; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2352.dto.TheDto; +import org.mapstruct.ap.test.bugs._2352.dto.TheModels; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = TheModelMapper.class) +public interface TheModelsMapper { + + TheModelsMapper INSTANCE = Mappers.getMapper( TheModelsMapper.class ); + + List convert(TheModels theModels); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2356/Issue2356Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2356/Issue2356Mapper.java new file mode 100644 index 0000000000..173645d118 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2356/Issue2356Mapper.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.ap.test.bugs._2356; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2356Mapper { + + Issue2356Mapper INSTANCE = Mappers.getMapper( Issue2356Mapper.class ); + + class Car { + //CHECKSTYLE:OFF + public String brand; + public String model; + public String modelInternational; + //CHECKSTYLE:ON + } + + class CarDTO { + //CHECKSTYLE:OFF + public String brand; + public String modelName; + //CHECKSTYLE:ON + } + + // When using InheritInverseConfiguration the mapping from in to modelName should be ignored + // and shouldn't lead to a compile error. + @Mapping(target = "modelName", source = "in") + CarDTO map(Car in); + + default String mapToModel(Car in) { + return in.modelInternational == null ? in.model : in.modelInternational; + } + + @InheritInverseConfiguration + @Mapping(target = "model", source = "modelName") + @Mapping(target = "modelInternational", ignore = true) + Car map(CarDTO in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2356/Issue2356Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2356/Issue2356Test.java new file mode 100644 index 0000000000..8efc38abd2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2356/Issue2356Test.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.ap.test.bugs._2356; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2356") +@WithClasses({ + Issue2356Mapper.class +}) +public class Issue2356Test { + + @ProcessorTest + public void shouldCompile() { + Issue2356Mapper.Car car = new Issue2356Mapper.Car(); + car.brand = "Tesla"; + car.model = "X"; + car.modelInternational = "3"; + Issue2356Mapper.CarDTO dto = Issue2356Mapper.INSTANCE.map( car ); + + assertThat( dto ).isNotNull(); + assertThat( dto.brand ).isEqualTo( "Tesla" ); + assertThat( dto.modelName ).isEqualTo( "3" ); + + car = Issue2356Mapper.INSTANCE.map( dto ); + + assertThat( car ).isNotNull(); + assertThat( car.brand ).isEqualTo( "Tesla" ); + assertThat( car.model ).isEqualTo( "3" ); + assertThat( car.modelInternational ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Address.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Address.java new file mode 100644 index 0000000000..8651165d82 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Address.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.ap.test.bugs._2393; + +/** + * @author Filip Hrisafov + */ +public class Address { + + private final String city; + private final Country country; + + public Address(String city, Country country) { + this.city = city; + this.country = country; + } + + public String getCity() { + return city; + } + + public Country getCountry() { + return country; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/AddressDto.java new file mode 100644 index 0000000000..41f771f319 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/AddressDto.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.ap.test.bugs._2393; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +public class AddressDto { + + private String city; + private CountryDto country; + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public CountryDto getCountry() { + return country; + } + + public void setCountry(CountryDto country) { + this.country = country; + } + + @Mapper(uses = CountryDto.Converter.class) + public interface Converter { + + Converter INSTANCE = Mappers.getMapper( Converter.class ); + + AddressDto convert(Address address); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Country.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Country.java new file mode 100644 index 0000000000..efeaad6fc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Country.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.ap.test.bugs._2393; + +/** + * @author Filip Hrisafov + */ +public class Country { + + private final String name; + + public Country(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/CountryDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/CountryDto.java new file mode 100644 index 0000000000..7e3751efb3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/CountryDto.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.ap.test.bugs._2393; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Filip Hrisafov + */ +public class CountryDto { + + private String name; + private String code; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + @Mapper + public interface Converter { + + @Mapping(target = "code", constant = "UNKNOWN") + CountryDto convert(Country from); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Issue2393Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Issue2393Test.java new file mode 100644 index 0000000000..8595d4c7f1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2393/Issue2393Test.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.test.bugs._2393; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2393") +@WithClasses({ + Address.class, + AddressDto.class, + Country.class, + CountryDto.class, +}) +public class Issue2393Test { + + @ProcessorTest + public void shouldUseCorrectImport() { + AddressDto dto = AddressDto.Converter.INSTANCE.convert( new Address( + "Zurich", + new Country( "Switzerland" ) + ) ); + + assertThat( dto.getCity() ).isEqualTo( "Zurich" ); + assertThat( dto.getCountry().getName() ).isEqualTo( "Switzerland" ); + assertThat( dto.getCountry().getCode() ).isEqualTo( "UNKNOWN" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/Issue2437Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/Issue2437Test.java new file mode 100644 index 0000000000..343b8fbf1f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/Issue2437Test.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.test.bugs._2437; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2437") +@WithClasses({ + Phone.class, + PhoneDto.class, + PhoneMapper.class, + PhoneParent1Mapper.class, + PhoneParent2Mapper.class, + PhoneSuperMapper.class, +}) +class Issue2437Test { + + @ProcessorTest + void shouldGenerateValidCode() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/Phone.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/Phone.java new file mode 100644 index 0000000000..98027861fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/Phone.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.ap.test.bugs._2437; + +/** + * @author Filip Hrisafov + */ +public class Phone { + + private final String number; + + public Phone(String number) { + this.number = number; + } + + public String getNumber() { + return number; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneDto.java new file mode 100644 index 0000000000..883e086e89 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneDto.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.ap.test.bugs._2437; + +/** + * @author Filip Hrisafov + */ +public class PhoneDto { + + private final String number; + + public PhoneDto(String number) { + this.number = number; + } + + public String getNumber() { + return number; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneMapper.java new file mode 100644 index 0000000000..16b01658be --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneMapper.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.ap.test.bugs._2437; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface PhoneMapper extends PhoneParent1Mapper, PhoneParent2Mapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneParent1Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneParent1Mapper.java new file mode 100644 index 0000000000..0b4da27c15 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneParent1Mapper.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.ap.test.bugs._2437; + +/** + * @author Filip Hrisafov + */ +public interface PhoneParent1Mapper extends PhoneSuperMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneParent2Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneParent2Mapper.java new file mode 100644 index 0000000000..77e9db6235 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneParent2Mapper.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.ap.test.bugs._2437; + +/** + * @author Filip Hrisafov + */ +public interface PhoneParent2Mapper extends PhoneSuperMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneSuperMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneSuperMapper.java new file mode 100644 index 0000000000..ba35b21d9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2437/PhoneSuperMapper.java @@ -0,0 +1,14 @@ +/* + * 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.test.bugs._2437; + +/** + * @author Filip Hrisafov + */ +public interface PhoneSuperMapper { + + Phone map(PhoneDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2439/ErroneousIssue2439Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2439/ErroneousIssue2439Mapper.java new file mode 100644 index 0000000000..a154d55fff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2439/ErroneousIssue2439Mapper.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._2439; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousIssue2439Mapper { + + @Mapping(target = "modeName", source = "mode.desc") + LiveDto map(LiveEntity entity); + + class LiveEntity { + private final LiveMode[] mode; + + public LiveEntity(LiveMode... mode) { + this.mode = mode; + } + + public LiveMode[] getMode() { + return mode; + } + } + + class LiveDto { + private String modeName; + + public String getModeName() { + return modeName; + } + + public void setModeName(String modeName) { + this.modeName = modeName; + } + } + + enum LiveMode { + TEST, + PROD, + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2439/Issuer2439Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2439/Issuer2439Test.java new file mode 100644 index 0000000000..c3c4702927 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2439/Issuer2439Test.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.test.bugs._2439; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2439") +@WithClasses({ + ErroneousIssue2439Mapper.class +}) +class Issuer2439Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousIssue2439Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "No property named \"mode.desc\" exists in source parameter(s)." + + " Type \"ErroneousIssue2439Mapper.LiveMode[]\" has no properties.") + } + ) + void shouldProvideGoodErrorMessage() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2478/Issue2478Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2478/Issue2478Mapper.java new file mode 100644 index 0000000000..3cec3e0b3c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2478/Issue2478Mapper.java @@ -0,0 +1,86 @@ +/* + * 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.test.bugs._2478; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2478Mapper { + + Issue2478Mapper INSTANCE = Mappers.getMapper( Issue2478Mapper.class ); + + ProductEntity productFromDto(Product dto, @Context Shop shop); + + class Product { + protected final String name; + protected final Shop shop; + + public Product(String name, Shop shop) { + this.name = name; + this.shop = shop; + } + + public String getName() { + return name; + } + + public Shop getShop() { + return shop; + } + } + + class Shop { + protected final String name; + + public Shop(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class ProductEntity { + + protected String name; + protected ShopEntity shop; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ShopEntity getShop() { + return shop; + } + + public void setShop(ShopEntity shop) { + this.shop = shop; + } + } + + class ShopEntity { + protected String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2478/Issue2478Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2478/Issue2478Test.java new file mode 100644 index 0000000000..05e21f4c40 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2478/Issue2478Test.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.ap.test.bugs._2478; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2478") +@WithClasses({ + Issue2478Mapper.class +}) +class Issue2478Test { + + @ProcessorTest + void shouldGenerateCodeThatCompiles() { + Issue2478Mapper.Product dto = new Issue2478Mapper.Product( "Test", new Issue2478Mapper.Shop( "Shopify" ) ); + Issue2478Mapper.ProductEntity entity = Issue2478Mapper.INSTANCE.productFromDto( dto, dto.getShop() ); + + assertThat( entity ).isNotNull(); + assertThat( entity.getName() ).isEqualTo( "Test" ); + assertThat( entity.getShop() ).isNotNull(); + assertThat( entity.getShop().getName() ).isEqualTo( "Shopify" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2501/Issue2501Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2501/Issue2501Mapper.java new file mode 100644 index 0000000000..08fa637791 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2501/Issue2501Mapper.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.ap.test.bugs._2501; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2501Mapper { + + Issue2501Mapper INSTANCE = Mappers.getMapper( Issue2501Mapper.class ); + + Customer map(CustomerDTO value); + + CustomerStatus map(DtoStatus status); + + default T unwrap(Optional optional) { + return optional.orElse( null ); + } + + enum CustomerStatus { + ENABLED, DISABLED, + } + + class Customer { + + private CustomerStatus status; + + public CustomerStatus getStatus() { + return status; + } + + public void setStatus(CustomerStatus stat) { + this.status = stat; + } + } + + enum DtoStatus { + ENABLED, DISABLED + } + + class CustomerDTO { + + private DtoStatus status; + + public Optional getStatus() { + return Optional.ofNullable( status ); + } + + public void setStatus(DtoStatus stat) { + this.status = stat; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2501/Issue2501Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2501/Issue2501Test.java new file mode 100644 index 0000000000..c5eb1e0aed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2501/Issue2501Test.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.test.bugs._2501; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2501Mapper.class +}) +class Issue2501Test { + + @ProcessorTest + void shouldUnwrapEnumOptional() { + Issue2501Mapper.CustomerDTO source = new Issue2501Mapper.CustomerDTO(); + source.setStatus( Issue2501Mapper.DtoStatus.DISABLED ); + + Issue2501Mapper.Customer target = Issue2501Mapper.INSTANCE.map( source ); + + assertThat( target.getStatus() ).isEqualTo( Issue2501Mapper.CustomerStatus.DISABLED ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2505/Issue2505Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2505/Issue2505Mapper.java new file mode 100644 index 0000000000..525eb09fd3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2505/Issue2505Mapper.java @@ -0,0 +1,51 @@ +/* + * 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.test.bugs._2505; + +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper( mappingControl = DeepClone.class ) +public interface Issue2505Mapper { + + Issue2505Mapper INSTANCE = Mappers.getMapper( Issue2505Mapper.class ); + + Customer map(CustomerDTO value); + + enum Status { + ENABLED, DISABLED, + } + + class Customer { + + private Status status; + + public Status getStatus() { + return status; + } + + public void setStatus(Status stat) { + this.status = stat; + } + } + + class CustomerDTO { + + private Status status; + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2505/Issue2505Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2505/Issue2505Test.java new file mode 100644 index 0000000000..b3fa6ea3c1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2505/Issue2505Test.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.test.bugs._2505; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak derksen + */ +@IssueKey("2505") +@WithClasses( Issue2505Mapper.class ) +class Issue2505Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( Issue2505Mapper.class ); + + @ProcessorTest + void shouldNotGenerateEnumMappingMethodForDeepClone() { + Issue2505Mapper.CustomerDTO source = new Issue2505Mapper.CustomerDTO(); + source.setStatus( Issue2505Mapper.Status.DISABLED ); + + Issue2505Mapper.Customer target = Issue2505Mapper.INSTANCE.map( source ); + + assertThat( target.getStatus() ).isEqualTo( Issue2505Mapper.Status.DISABLED ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2530/Issue2530Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2530/Issue2530Mapper.java new file mode 100644 index 0000000000..268237376c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2530/Issue2530Mapper.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.test.bugs._2530; + +import java.time.LocalDate; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Issue2530Mapper { + + Issue2530Mapper INSTANCE = Mappers.getMapper( Issue2530Mapper.class ); + + @Mapping(target = "date", source = ".", dateFormat = "yyyy-MM-dd") + Test map(String s); + + class Test { + + LocalDate date; + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate dateTime) { + this.date = dateTime; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2530/Issue2530Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2530/Issue2530Test.java new file mode 100644 index 0000000000..2999939a78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2530/Issue2530Test.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.test.bugs._2530; + +import java.time.Month; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ben Zegveld + */ +@IssueKey("2530") +@WithClasses({ + Issue2530Mapper.class +}) +public class Issue2530Test { + + @ProcessorTest + public void shouldConvert() { + Issue2530Mapper.Test target = Issue2530Mapper.INSTANCE.map( "2021-07-31" ); + + assertThat( target ).isNotNull(); + assertThat( target.getDate().getYear() ).isEqualTo( 2021 ); + assertThat( target.getDate().getMonth() ).isEqualTo( Month.JULY ); + assertThat( target.getDate().getDayOfMonth() ).isEqualTo( 31 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/ImplicitSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/ImplicitSourceTest.java new file mode 100644 index 0000000000..edcbbfd4d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/ImplicitSourceTest.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.ap.test.bugs._2537; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * implicit source-target mapping should list the source property as being mapped. + * + * @author Ben Zegveld + */ +@WithClasses( { UnmappedSourcePolicyWithImplicitSourceMapper.class } ) +@IssueKey( "2537" ) +public class ImplicitSourceTest { + + @ProcessorTest + public void situationCompilesWithoutErrors() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/UnmappedSourcePolicyWithImplicitSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/UnmappedSourcePolicyWithImplicitSourceMapper.java new file mode 100644 index 0000000000..21d8ca5332 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2537/UnmappedSourcePolicyWithImplicitSourceMapper.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.ap.test.bugs._2537; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +/** + * @author Ben Zegveld + */ +@Mapper( unmappedSourcePolicy = ReportingPolicy.ERROR ) +public interface UnmappedSourcePolicyWithImplicitSourceMapper { + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "property" ) + Target map(Source source); + + class Source { + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + } + + class Target { + private String property; + + public String getProperty() { + return property; + } + + public void setProperty(String property) { + this.property = property; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Group.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Group.java new file mode 100644 index 0000000000..4451431fee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Group.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.ap.test.bugs._2538; + +/** + * @author Filip Hrisafov + */ +public class Group { + + private final String id; + + public Group(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/GroupDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/GroupDto.java new file mode 100644 index 0000000000..0d94c90815 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/GroupDto.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.ap.test.bugs._2538; + +/** + * @author Filip Hrisafov + */ +public class GroupDto { + + private final String id; + + public GroupDto(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Issue2538Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Issue2538Test.java new file mode 100644 index 0000000000..cf99e67f92 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/Issue2538Test.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.ap.test.bugs._2538; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Group.class, + GroupDto.class, + TeamMapper.class, + TeamRole.class, + TeamRoleDto.class, +}) +class Issue2538Test { + + @ProcessorTest + void shouldCorrectlyUseQualifiedMethodIn2StepMapping() { + TeamRole role = TeamMapper.INSTANCE.mapUsingFirstLookup( new TeamRoleDto( "test" ) ); + + assertThat( role ).isNotNull(); + assertThat( role.getGroup() ).isNotNull(); + assertThat( role.getGroup().getId() ).isEqualTo( "lookup-test" ); + + role = TeamMapper.INSTANCE.mapUsingSecondLookup( new TeamRoleDto( "test" ) ); + + assertThat( role ).isNotNull(); + assertThat( role.getGroup() ).isNotNull(); + assertThat( role.getGroup().getId() ).isEqualTo( "second-test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamMapper.java new file mode 100644 index 0000000000..5615e7bc43 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamMapper.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.ap.test.bugs._2538; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface TeamMapper { + + TeamMapper INSTANCE = Mappers.getMapper( TeamMapper.class ); + + // This method is testing methodX(methodY(...)) where methodY is qualified + @Mapping(target = "group", source = "groupId", qualifiedByName = "firstLookup") + TeamRole mapUsingFirstLookup(TeamRoleDto in); + + // This method is testing methodX(methodY(...)) where methodX is qualified + @Mapping(target = "group", source = "groupId", qualifiedByName = "secondLookup") + TeamRole mapUsingSecondLookup(TeamRoleDto in); + + Group map(GroupDto in); + + @Named("firstLookup") + default GroupDto lookupGroup(String groupId) { + return groupId != null ? new GroupDto( "lookup-" + groupId ) : null; + } + + default GroupDto normalLookup(String groupId) { + return groupId != null ? new GroupDto( groupId ) : null; + } + + @Named("secondLookup") + default Group mapSecondLookup(GroupDto in) { + return in != null ? new Group( "second-" + in.getId() ) : null; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRole.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRole.java new file mode 100644 index 0000000000..f73700fa4d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRole.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.ap.test.bugs._2538; + +/** + * @author Filip Hrisafov + */ +public class TeamRole { + + private final Group group; + + public TeamRole(Group group) { + this.group = group; + } + + public Group getGroup() { + return group; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRoleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRoleDto.java new file mode 100644 index 0000000000..fac5a1a9ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2538/TeamRoleDto.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.ap.test.bugs._2538; + +/** + * @author Filip Hrisafov + */ +public class TeamRoleDto { + + private final String groupId; + + public TeamRoleDto(String groupId) { + this.groupId = groupId; + } + + public String getGroupId() { + return groupId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Mapper.java new file mode 100644 index 0000000000..0531f5242c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Mapper.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._2541; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2541Mapper { + + Issue2541Mapper INSTANCE = Mappers.getMapper( Issue2541Mapper.class ); + + Target map(Source source); + + default Optional toOptional(@Nullable T value) { + return Optional.ofNullable( value ); + } + + class Target { + private Optional value; + + public Optional getValue() { + return value; + } + + public void setValue(Optional value) { + this.value = value; + } + } + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Test.java new file mode 100644 index 0000000000..d1b8bd7f52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Issue2541Test.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.ap.test.bugs._2541; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2541Mapper.class, + Nullable.class, +}) +class Issue2541Test { + + @ProcessorTest + void shouldGenerateCorrectCode() { + Issue2541Mapper.Target target = Issue2541Mapper.INSTANCE.map( new Issue2541Mapper.Source( null ) ); + + assertThat( target.getValue() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Nullable.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Nullable.java new file mode 100644 index 0000000000..24c52eb906 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2541/Nullable.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.ap.test.bugs._2541; + +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; + +@Target({ + ElementType.METHOD, + ElementType.TYPE_USE +}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Nullable { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Mapper.java new file mode 100644 index 0000000000..6d02d709a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Mapper.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.test.bugs._2544; + +import java.math.BigDecimal; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Issue2544Mapper { + + Issue2544Mapper INSTANCE = Mappers.getMapper( Issue2544Mapper.class ); + + @Mapping( target = "bigNumber", source = ".", numberFormat = "##0.#####E0" ) + Target map(String s); + + class Target { + + private BigDecimal bigNumber; + + public BigDecimal getBigNumber() { + return bigNumber; + } + + public void setBigNumber(BigDecimal bigNumber) { + this.bigNumber = bigNumber; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Test.java new file mode 100644 index 0000000000..4f58f81d08 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2544/Issue2544Test.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.ap.test.bugs._2544; + +import java.math.BigDecimal; + +import org.junitpioneer.jupiter.DefaultLocale; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2544" ) +@WithClasses( { Issue2544Mapper.class } ) +public class Issue2544Test { + // Parsing numbers is sensitive to locale settings (e.g. decimal point) + + @ProcessorTest + @DefaultLocale("en") + public void shouldConvertEn() { + Issue2544Mapper.Target target = Issue2544Mapper.INSTANCE.map( "123.45679E6" ); + + assertThat( target ).isNotNull(); + assertThat( target.getBigNumber() ).isEqualTo( new BigDecimal( "1.2345679E+8" ) ); + } + + @ProcessorTest + @DefaultLocale("de") + public void shouldConvertDe() { + Issue2544Mapper.Target target = Issue2544Mapper.INSTANCE.map( "123,45679E6" ); + + assertThat( target ).isNotNull(); + assertThat( target.getBigNumber() ).isEqualTo( new BigDecimal( "1.2345679E+8" ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Mapper.java new file mode 100644 index 0000000000..f4d3c3d22d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Mapper.java @@ -0,0 +1,76 @@ +/* + * 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.test.bugs._2624; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2624Mapper { + + Issue2624Mapper INSTANCE = Mappers.getMapper( Issue2624Mapper.class ); + + @Mapping( target = "department.id", source = "did") + @Mapping( target = "department.name", source = "dname") + Employee fromMap(Map source); + + class Employee { + private String id; + private String name; + private Department department; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Department getDepartment() { + return department; + } + + public void setDepartment(Department department) { + this.department = department; + } + } + + class Department { + private String id; + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Test.java new file mode 100644 index 0000000000..2fb04ef3a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2624/Issue2624Test.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.ap.test.bugs._2624; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2624Mapper.class +}) +class Issue2624Test { + + @ProcessorTest + void shouldCorrectlyMapNestedTargetFromMap() { + Map map = new HashMap<>(); + map.put( "id", "1234" ); + map.put( "name", "Tester" ); + map.put( "did", "4321" ); //Department Id + map.put( "dname", "Test" ); // Department name + + Issue2624Mapper.Employee employee = Issue2624Mapper.INSTANCE.fromMap( map ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1234" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + Issue2624Mapper.Department department = employee.getDepartment(); + assertThat( department ).isNotNull(); + assertThat( department.getId() ).isEqualTo( "4321" ); + assertThat( department.getName() ).isEqualTo( "Test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Mapper.java new file mode 100644 index 0000000000..0ac236372a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Mapper.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.ap.test.bugs._2663; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper( uses = NullableHelper.class ) +public interface Issue2663Mapper { + + Issue2663Mapper INSTANCE = Mappers.getMapper( Issue2663Mapper.class ); + + Request map(RequestDto dto); + + default JsonNullable mapJsonNullableChildren(JsonNullable dtos) { + if ( dtos.isPresent() ) { + return JsonNullable.of( mapChild( dtos.get() ) ); + } + else { + return JsonNullable.undefined(); + } + } + + Request.ChildRequest mapChild(RequestDto.ChildRequestDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Test.java new file mode 100644 index 0000000000..efd14b1d36 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Issue2663Test.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._2663; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey( "2663" ) +@WithClasses({ + Issue2663Mapper.class, + JsonNullable.class, + Nullable.class, + NullableHelper.class, + Request.class, + RequestDto.class +}) +public class Issue2663Test { + + @ProcessorTest + public void shouldUnpackGenericsCorrectly() { + RequestDto dto = new RequestDto(); + dto.setName( JsonNullable.of( "Tester" ) ); + + Request request = Issue2663Mapper.INSTANCE.map( dto ); + + assertThat( request.getName() ) + .extracting( Nullable::get ) + .isEqualTo( "Tester" ); + + dto.setName( JsonNullable.undefined() ); + + request = Issue2663Mapper.INSTANCE.map( dto ); + + assertThat( request.getName() ) + .extracting( Nullable::isPresent ) + .isEqualTo( false ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/JsonNullable.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/JsonNullable.java new file mode 100644 index 0000000000..0c1aeaba93 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/JsonNullable.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.ap.test.bugs._2663; + +import java.util.NoSuchElementException; + +/** + * @author Filip Hrisafov + */ +public class JsonNullable { + + private static final JsonNullable UNDEFINED = new JsonNullable<>( null, false ); + + private final T value; + private final boolean present; + + private JsonNullable(T value, boolean present) { + this.value = value; + this.present = present; + } + + public T get() { + if ( !present ) { + throw new NoSuchElementException("Value is undefined"); + } + return value; + } + + public boolean isPresent() { + return present; + } + + @SuppressWarnings("unchecked") + public static JsonNullable undefined() { + return (JsonNullable) UNDEFINED; + } + + public static JsonNullable of(T value) { + return new JsonNullable<>( value, true ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Nullable.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Nullable.java new file mode 100644 index 0000000000..6701793ed9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Nullable.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.ap.test.bugs._2663; + +import java.util.NoSuchElementException; + +/** + * @author Filip Hrisafov + */ +public class Nullable { + + @SuppressWarnings("rawtypes") + private static final Nullable UNDEFINED = new Nullable<>( null, false ); + + private final T value; + private final boolean present; + + private Nullable(T value, boolean present) { + this.value = value; + this.present = present; + } + + public T get() { + if ( !present ) { + throw new NoSuchElementException("Value is undefined"); + } + return value; + } + + public boolean isPresent() { + return present; + } + + public static Nullable of(T value) { + return new Nullable<>( value, true ); + } + + @SuppressWarnings("unchecked") + public static Nullable undefined() { + return (Nullable) UNDEFINED; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/NullableHelper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/NullableHelper.java new file mode 100644 index 0000000000..ae68300e62 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/NullableHelper.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.ap.test.bugs._2663; + +/** + * @author Filip Hrisafov + */ +public class NullableHelper { + + private NullableHelper() { + // Helper class + } + + public static Nullable jsonNullableToNullable(JsonNullable jsonNullable) { + if ( jsonNullable.isPresent() ) { + return Nullable.of( jsonNullable.get() ); + } + return Nullable.undefined(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Request.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Request.java new file mode 100644 index 0000000000..8b58e2e0b5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/Request.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.ap.test.bugs._2663; + +/** + * @author Filip Hrisafov + */ +public class Request { + + private Nullable name = Nullable.undefined(); + private Nullable child = Nullable.undefined(); + + public Nullable getName() { + return name; + } + + public void setName(Nullable name) { + this.name = name; + } + + public Nullable getChild() { + return child; + } + + public void setChild(Nullable child) { + this.child = child; + } + + public static class ChildRequest { + + private Nullable name = Nullable.undefined(); + + public Nullable getName() { + return name; + } + + public void setName(Nullable name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/RequestDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/RequestDto.java new file mode 100644 index 0000000000..d1a93a9bd4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2663/RequestDto.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.ap.test.bugs._2663; + +/** + * @author Filip Hrisafov + */ +public class RequestDto { + + private JsonNullable name = JsonNullable.undefined(); + private JsonNullable child = JsonNullable.undefined(); + + public JsonNullable getName() { + return name; + } + + public void setName(JsonNullable name) { + this.name = name; + } + + public JsonNullable getChild() { + return child; + } + + public void setChild(JsonNullable child) { + this.child = child; + } + + public static class ChildRequestDto { + + private JsonNullable name = JsonNullable.undefined(); + + public JsonNullable getName() { + return name; + } + + public void setName(JsonNullable name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668ListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668ListMapper.java new file mode 100644 index 0000000000..beb1d3283a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668ListMapper.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.ap.test.bugs._2668; + +import java.util.ArrayList; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Erroneous2668ListMapper { + + Erroneous2668ListMapper INSTANCE = Mappers.getMapper( Erroneous2668ListMapper.class ); + + CollectionTarget map(CollectionSource source); + + class CollectionTarget { + MyArrayList list; + + public MyArrayList getList() { + return list; + } + + public void setList(MyArrayList list) { + this.list = list; + } + } + + class CollectionSource { + MyArrayList list; + + public MyArrayList getList() { + return list; + } + + public void setList(MyArrayList list) { + this.list = list; + } + } + + class MyArrayList extends ArrayList { + private String unusable; + + public MyArrayList(String unusable) { + this.unusable = unusable; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668MapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668MapMapper.java new file mode 100644 index 0000000000..3c9bcd3926 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Erroneous2668MapMapper.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.ap.test.bugs._2668; + +import java.util.HashMap; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Erroneous2668MapMapper { + + Erroneous2668MapMapper INSTANCE = Mappers.getMapper( Erroneous2668MapMapper.class ); + + CollectionTarget map(CollectionSource source); + + class CollectionTarget { + MyHashMap map; + + public MyHashMap getMap() { + return map; + } + + public void setMap(MyHashMap map) { + this.map = map; + } + } + + class CollectionSource { + MyHashMap map; + + public MyHashMap getMap() { + return map; + } + + public void setMap(MyHashMap map) { + this.map = map; + } + } + + class MyHashMap extends HashMap { + private String unusable; + + public MyHashMap(String unusable) { + this.unusable = unusable; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Mapper.java new file mode 100644 index 0000000000..1b72e2f636 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Mapper.java @@ -0,0 +1,120 @@ +/* + * 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.test.bugs._2668; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Issue2668Mapper { + + Issue2668Mapper INSTANCE = Mappers.getMapper( Issue2668Mapper.class ); + + CollectionTarget map(CollectionSource source); + + class CollectionTarget { + MyArrayList list; + MyHashMap map; + MyCopyArrayList copyList; + MyCopyHashMap copyMap; + + public MyArrayList getList() { + return list; + } + + public MyHashMap getMap() { + return map; + } + + public void setList(MyArrayList list) { + this.list = list; + } + + public void setMap(MyHashMap map) { + this.map = map; + } + + public MyCopyArrayList getCopyList() { + return copyList; + } + + public MyCopyHashMap getCopyMap() { + return copyMap; + } + + public void setCopyList(MyCopyArrayList copyList) { + this.copyList = copyList; + } + + public void setCopyMap(MyCopyHashMap copyMap) { + this.copyMap = copyMap; + } + } + + class CollectionSource { + MyArrayList list; + MyHashMap map; + MyCopyArrayList copyList; + MyCopyHashMap copyMap; + + public MyArrayList getList() { + return list; + } + + public MyHashMap getMap() { + return map; + } + + public void setList(MyArrayList list) { + this.list = list; + } + + public void setMap(MyHashMap map) { + this.map = map; + } + + public MyCopyArrayList getCopyList() { + return copyList; + } + + public MyCopyHashMap getCopyMap() { + return copyMap; + } + + public void setCopyList(MyCopyArrayList copyList) { + this.copyList = copyList; + } + + public void setCopyMap(MyCopyHashMap copyMap) { + this.copyMap = copyMap; + } + } + + class MyArrayList extends ArrayList { + } + + class MyHashMap extends HashMap { + } + + class MyCopyArrayList extends ArrayList { + public MyCopyArrayList(Collection copy) { + super( copy ); + } + } + + class MyCopyHashMap extends HashMap { + public MyCopyHashMap(HashMap copy) { + super( copy ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Test.java new file mode 100644 index 0000000000..20b4e445fd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2668/Issue2668Test.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.ap.test.bugs._2668; + +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2668" ) +class Issue2668Test { + + @ProcessorTest + @WithClasses( Issue2668Mapper.class ) + void shouldCompileCorrectlyWithAvailableConstructors() { + } + + @ProcessorTest + @WithClasses( Erroneous2668ListMapper.class ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = Kind.ERROR, + line = 21, + message = "org.mapstruct.ap.test.bugs._2668.Erroneous2668ListMapper.MyArrayList" + + " does not have an accessible copy or no-args constructor." + ) + } + ) + void errorExpectedBecauseCollectionIsNotUsable() { + } + + @ProcessorTest + @WithClasses( Erroneous2668MapMapper.class ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = Kind.ERROR, + line = 21, + message = "org.mapstruct.ap.test.bugs._2668.Erroneous2668MapMapper.MyHashMap" + + " does not have an accessible copy or no-args constructor." + ) + } + ) + void errorExpectedBecauseMapIsNotUsable() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Mapper.java new file mode 100644 index 0000000000..221197d18f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Mapper.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.ap.test.bugs._2673; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Issue2673Mapper { + + Issue2673Mapper INSTANCE = Mappers.getMapper( Issue2673Mapper.class ); + + Target map(Source source); + + default T map(Optional opt) { + return opt.orElse( null ); + } + + default Optional map(T t) { + return Optional.ofNullable( t ); + } + + class Target { + private final int primitive; + private final String nonPrimitive; + + public Target(int primitive, String nonPrimitive) { + this.primitive = primitive; + this.nonPrimitive = nonPrimitive; + } + + public int getPrimitive() { + return primitive; + } + + public String getNonPrimitive() { + return nonPrimitive; + } + } + + class Source { + private final int primitive; + private final Optional nonPrimitive; + + public Source(int primitive, Optional nonPrimitive) { + this.primitive = primitive; + this.nonPrimitive = nonPrimitive; + } + + public int getPrimitive() { + return primitive; + } + + public Optional getNonPrimitive() { + return nonPrimitive; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Test.java new file mode 100644 index 0000000000..40a5e79276 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2673/Issue2673Test.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.ap.test.bugs._2673; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Ben Zegveld + */ +@WithClasses({ + Issue2673Mapper.class +}) +class Issue2673Test { + + @ProcessorTest + @IssueKey( "2673" ) + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/ErroneousSourceTargetMapping.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/ErroneousSourceTargetMapping.java new file mode 100644 index 0000000000..9157a626b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/ErroneousSourceTargetMapping.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.ap.test.bugs._2674; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +interface ErroneousSourceTargetMapping { + + ErroneousSourceTargetMapping INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapping.class ); + + @BeforeMapping + void beforeMappingMethod(Target target, @MappingTarget Source source); + + @AfterMapping + void afterMappingMethod(Source source, @MappingTarget Target target); + + Target toTarget(Source source); + + Source toSource(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Issue2674Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Issue2674Test.java new file mode 100644 index 0000000000..90530051fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Issue2674Test.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._2674; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static javax.tools.Diagnostic.Kind; + +/** + * @author Justyna Kubica-Ledzion + */ +@IssueKey("2674") +@WithClasses({ Source.class, Target.class, ErroneousSourceTargetMapping.class }) +public class Issue2674Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSourceTargetMapping.class, + kind = Kind.ERROR, + line = 20, + message = "@BeforeMapping can only be applied to an implemented method."), + @Diagnostic(type = ErroneousSourceTargetMapping.class, + kind = Kind.ERROR, + line = 23, + message = "@AfterMapping can only be applied to an implemented method.") + } + ) + public void shouldRaiseErrorIfThereIsNoAfterOrBeforeMethodImplementation() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Source.java new file mode 100644 index 0000000000..f9145c08b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Source.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.ap.test.bugs._2674; + +public class Source { + + private int id; + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Target.java new file mode 100644 index 0000000000..244bdeb9a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2674/Target.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.ap.test.bugs._2674; + +public class Target { + + private int id; + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Mapper.java new file mode 100644 index 0000000000..e74ad1e770 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Mapper.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.ap.test.bugs._2677; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2677Mapper { + + Issue2677Mapper INSTANCE = Mappers.getMapper( Issue2677Mapper.class ); + + @Mapping(target = "id", source = "value.id") + Output map(Wrapper in); + + @Mapping(target = ".", source = "value") + Output mapImplicitly(Wrapper in); + + @Mapping(target = "id", source = "value.id") + Output mapFromParent(Wrapper in); + + @Mapping(target = "id", source = "value.id") + Output mapFromChild(Wrapper in); + + @Mapping( target = "value", source = "wrapperValue") + Wrapper mapToWrapper(String wrapperValue, Wrapper wrapper); + + @Mapping(target = "id", source = "value.id") + Output mapWithPresenceCheck(Wrapper in); + + class Wrapper { + private final T value; + private final String status; + + public Wrapper(T value, String status) { + this.value = value; + this.status = status; + } + + public String getStatus() { + return status; + } + + public T getValue() { + return value; + } + } + + class Parent { + private final int id; + + public Parent(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } + + class Child extends Parent { + private final String whatever; + + public Child(int id, String whatever) { + super( id ); + this.whatever = whatever; + } + + public String getWhatever() { + return whatever; + } + } + + class ParentWithPresenceCheck { + private final int id; + + public ParentWithPresenceCheck(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public boolean hasId() { + return id > 10; + } + } + + class Output { + private int id; + private String status; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Test.java new file mode 100644 index 0000000000..0eb4d6d794 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2677/Issue2677Test.java @@ -0,0 +1,92 @@ +/* + * 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.test.bugs._2677; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2677") +@WithClasses({ + Issue2677Mapper.class +}) +class Issue2677Test { + + @ProcessorTest + void shouldCorrectlyUseGenericsWithExtends() { + Issue2677Mapper.Parent parent = new Issue2677Mapper.Parent( 10 ); + Issue2677Mapper.Child child = new Issue2677Mapper.Child( 15, "Test" ); + + Issue2677Mapper.Output output = Issue2677Mapper.INSTANCE.map( new Issue2677Mapper.Wrapper<>( + parent, + "extends" + ) ); + + assertThat( output.getStatus() ).isEqualTo( "extends" ); + assertThat( output.getId() ).isEqualTo( 10 ); + + output = Issue2677Mapper.INSTANCE.mapFromChild( new Issue2677Mapper.Wrapper<>( + child, + "child" + ) ); + + assertThat( output.getStatus() ).isEqualTo( "child" ); + assertThat( output.getId() ).isEqualTo( 15 ); + + output = Issue2677Mapper.INSTANCE.mapFromParent( new Issue2677Mapper.Wrapper<>( + parent, + "parent" + ) ); + + assertThat( output.getStatus() ).isEqualTo( "parent" ); + assertThat( output.getId() ).isEqualTo( 10 ); + + output = Issue2677Mapper.INSTANCE.mapImplicitly( new Issue2677Mapper.Wrapper<>( + child, + "implicit" + ) ); + + assertThat( output.getStatus() ).isEqualTo( "implicit" ); + assertThat( output.getId() ).isEqualTo( 15 ); + + Issue2677Mapper.Wrapper result = Issue2677Mapper.INSTANCE.mapToWrapper( + "test", + new Issue2677Mapper.Wrapper<>( + child, + "super" + ) + ); + + assertThat( result.getStatus() ).isEqualTo( "super" ); + assertThat( result.getValue() ).isEqualTo( "test" ); + + output = Issue2677Mapper.INSTANCE.mapWithPresenceCheck( + new Issue2677Mapper.Wrapper<>( + new Issue2677Mapper.ParentWithPresenceCheck( 8 ), + "presenceCheck" + ) + ); + + assertThat( output.getStatus() ).isEqualTo( "presenceCheck" ); + assertThat( output.getId() ).isEqualTo( 0 ); + + output = Issue2677Mapper.INSTANCE.mapWithPresenceCheck( + new Issue2677Mapper.Wrapper<>( + new Issue2677Mapper.ParentWithPresenceCheck( 15 ), + "presenceCheck" + ) + ); + + assertThat( output.getStatus() ).isEqualTo( "presenceCheck" ); + assertThat( output.getId() ).isEqualTo( 15 ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/Issue2704Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/Issue2704Test.java new file mode 100644 index 0000000000..5920a6022b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/Issue2704Test.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.ap.test.bugs._2704; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Valentin Kulesh + */ +@IssueKey("2704") +@WithClasses({ TestMapper.class, TopLevel.class }) +public class Issue2704Test { + @ProcessorTest + public void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TestMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TestMapper.java new file mode 100644 index 0000000000..846a783437 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TestMapper.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.ap.test.bugs._2704; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.bugs._2704.TopLevel.Target; + +/** + * @author Valentin Kulesh + */ +@Mapper(implementationPackage = "") +public interface TestMapper { + @Mapping(target = "e", constant = "VALUE1") + Target test(Object unused); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TopLevel.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TopLevel.java new file mode 100644 index 0000000000..ef30c2cd6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2704/TopLevel.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.ap.test.bugs._2704; + +/** + * @author Valentin Kulesh + */ +public interface TopLevel { + enum InnerEnum { + VALUE1, + VALUE2, + } + + class Target { + public void setE(@SuppressWarnings("unused") InnerEnum e) { + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java new file mode 100644 index 0000000000..db3c0e6d49 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Mapper.java @@ -0,0 +1,75 @@ +/* + * 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.test.bugs._2743; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue2743Mapper { + + @BeanMapping(ignoreUnmappedSourceProperties = { "number" }) + Target map(Source source); + + class Source { + + private final int number = 10; + private final NestedSource nested; + + public Source(NestedSource nested) { + this.nested = nested; + } + + public int getNumber() { + return number; + } + + public NestedSource getNested() { + return nested; + } + } + + class NestedSource { + private final String value; + + public NestedSource(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final NestedTarget nested; + + public Target(NestedTarget nested) { + this.nested = nested; + } + + public NestedTarget getNested() { + return nested; + } + } + + class NestedTarget { + private final String value; + + public NestedTarget(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.java new file mode 100644 index 0000000000..5c2bb61279 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2743/Issue2743Test.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.ap.test.bugs._2743; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2743") +@WithClasses({ + Issue2743Mapper.class +}) +class Issue2743Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Mapper.java new file mode 100644 index 0000000000..0062667df4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Mapper.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._2748; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2748Mapper { + + Issue2748Mapper INSTANCE = Mappers.getMapper( Issue2748Mapper.class ); + + @Mapping(target = "specificValue", source = "annotations.specific/value") + Target map(Source source); + + class Target { + private final String specificValue; + + public Target(String specificValue) { + this.specificValue = specificValue; + } + + public String getSpecificValue() { + return specificValue; + } + } + + class Source { + private final Map annotations; + + public Source(Map annotations) { + this.annotations = annotations; + } + + public Map getAnnotations() { + return annotations; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Test.java new file mode 100644 index 0000000000..4bda2cd43d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2748/Issue2748Test.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.test.bugs._2748; + +import java.util.Collections; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2748") +@WithClasses( { + Issue2748Mapper.class +} ) +class Issue2748Test { + + @ProcessorTest + void shouldMapNonJavaIdentifier() { + Map annotations = Collections.singletonMap( "specific/value", "value" ); + Issue2748Mapper.Source source = new Issue2748Mapper.Source( annotations ); + + Issue2748Mapper.Target target = Issue2748Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getSpecificValue() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.java new file mode 100644 index 0000000000..9abdf560c4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Mapper.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.test.bugs._2781; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Mengxing Yuan + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue2781Mapper { + + Issue2781Mapper INSTANCE = Mappers.getMapper( Issue2781Mapper.class ); + + @Mapping(target = "nested", source = "source") + Target map(Source source); + + class Target { + private Source nested; + + public Source getNested() { + return nested; + } + + public void setNested(Source nested) { + this.nested = nested; + } + } + + class Source { + private String field1; + private String field2; + + public Source(String field1, String field2) { + this.field1 = field1; + this.field2 = field2; + } + + public String getField1() { + return field1; + } + + public String getField2() { + return field2; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.java new file mode 100644 index 0000000000..80ac4f5ce3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2781/Issue2781Test.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.ap.test.bugs._2781; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Mengxing Yuan + */ +@IssueKey("2781") +@WithClasses({ + Issue2781Mapper.class +}) +class Issue2781Test { + + @ProcessorTest + void shouldCompileWithoutErrors() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Mapper.java new file mode 100644 index 0000000000..41f29da920 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Mapper.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.test.bugs._2795; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +@Mapper +public interface Issue2795Mapper { + + void update(Source update, @MappingTarget Target destination); + + void update(NestedDto update, @MappingTarget Nested destination); + + static T unwrap(Optional optional) { + return optional.orElse( null ); + } + + @Condition + static boolean isNotEmpty(Optional field) { + return field != null && field.isPresent(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Test.java new file mode 100644 index 0000000000..4b7b599527 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Issue2795Test.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.ap.test.bugs._2795; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("2795") +@WithClasses({ + Issue2795Mapper.class, + Nested.class, + NestedDto.class, + Target.class, + Source.class, +}) +public class Issue2795Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Nested.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Nested.java new file mode 100644 index 0000000000..5214c7d076 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Nested.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.ap.test.bugs._2795; + +public class Nested { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/NestedDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/NestedDto.java new file mode 100644 index 0000000000..1b27453435 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/NestedDto.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.ap.test.bugs._2795; + +public class NestedDto { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Source.java new file mode 100644 index 0000000000..927e2b09f5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Source.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.ap.test.bugs._2795; + +import java.util.Optional; + +public class Source { + + private Optional nested = Optional.empty(); + + public Optional getNested() { + return nested; + } + + public void setNested(Optional nested) { + this.nested = nested; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Target.java new file mode 100644 index 0000000000..ed69a6d48f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2795/Target.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.ap.test.bugs._2795; + +public class Target { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleDto.java new file mode 100644 index 0000000000..eff9f26271 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleDto.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.ap.test.bugs._2797; + +/** + * @author Ben Zegveld + */ +public class ExampleDto { + + private String personFirstName; + private String personLastName; + + public String getPersonFirstName() { + return personFirstName; + } + + public String getPersonLastName() { + return personLastName; + } + + public void setPersonFirstName(String personFirstName) { + this.personFirstName = personFirstName; + } + + public void setPersonLastName(String personLastName) { + this.personLastName = personLastName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleMapper.java new file mode 100644 index 0000000000..eccac27256 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/ExampleMapper.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.ap.test.bugs._2797; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.bugs._2797.model.Example.Person; + +import static org.mapstruct.ReportingPolicy.ERROR; + +/** + * @author Ben Zegveld + */ +@Mapper(unmappedTargetPolicy = ERROR) +public interface ExampleMapper { + + @Mapping(target = "personFirstName", source = "names.first") + @Mapping(target = "personLastName", source = "names.last") + ExampleDto map(Person person); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/Issue2797Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/Issue2797Test.java new file mode 100644 index 0000000000..7f3dd5f3de --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/Issue2797Test.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.ap.test.bugs._2797; + +import org.mapstruct.ap.test.bugs._2797.model.BasePerson; +import org.mapstruct.ap.test.bugs._2797.model.Example; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2797" ) +@WithClasses( { ExampleDto.class, ExampleMapper.class, Example.class, BasePerson.class } ) +public class Issue2797Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/BasePerson.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/BasePerson.java new file mode 100644 index 0000000000..6b54e6ee31 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/BasePerson.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.ap.test.bugs._2797.model; + +/** + * @author Ben Zegveld + */ +public class BasePerson { + + private Names names; + + public Names getNames() { + return names; + } + + public void setNames(Names names) { + this.names = names; + } + + public static class Names { + + private String first; + private String last; + + public String getFirst() { + return first; + } + + public String getLast() { + return last; + } + + public void setFirst(String first) { + this.first = first; + } + + public void setLast(String last) { + this.last = last; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/Example.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/Example.java new file mode 100644 index 0000000000..4dfa40f695 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2797/model/Example.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._2797.model; + +/** + * @author Ben Zegveld + */ +public class Example { + + public static class Person extends BasePerson { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.java new file mode 100644 index 0000000000..102a9d26eb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/Issue2807Test.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.ap.test.bugs._2807.spring; + +import org.mapstruct.ap.test.bugs._2807.spring.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.spring.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget.BeforeWithTarget; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2807" ) +public class Issue2807Test { + + @ProcessorTest + @WithSpring + @WithClasses( { SpringLifeCycleMapper.class, BeforeMethod.class, BeforeWithTarget.class, AfterMethod.class } ) + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.java new file mode 100644 index 0000000000..408a2ca3e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/SpringLifeCycleMapper.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.ap.test.bugs._2807.spring; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.bugs._2807.spring.after.AfterMethod; +import org.mapstruct.ap.test.bugs._2807.spring.before.BeforeMethod; +import org.mapstruct.ap.test.bugs._2807.spring.beforewithtarget.BeforeWithTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper( componentModel = "spring", uses = { BeforeMethod.class, AfterMethod.class, + BeforeWithTarget.class }, unmappedTargetPolicy = ReportingPolicy.IGNORE ) +public interface SpringLifeCycleMapper { + SpringLifeCycleMapper INSTANCE = Mappers.getMapper( SpringLifeCycleMapper.class ); + + List map(List list); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.java new file mode 100644 index 0000000000..05770c6603 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/after/AfterMethod.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.ap.test.bugs._2807.spring.after; + +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.MappingTarget; + +/** + * @author Ben Zegveld + */ +public class AfterMethod { + private AfterMethod() { + } + + @AfterMapping + public static void doNothing(@MappingTarget List source) { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.java new file mode 100644 index 0000000000..9fb9e7e883 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/before/BeforeMethod.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.ap.test.bugs._2807.spring.before; + +import org.mapstruct.BeforeMapping; + +/** + * @author Ben Zegveld + */ +public class BeforeMethod { + private BeforeMethod() { + } + + @BeforeMapping + public static void doNothing(Iterable source) { + return; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.java new file mode 100644 index 0000000000..a3ee8b57ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2807/spring/beforewithtarget/BeforeWithTarget.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.ap.test.bugs._2807.spring.beforewithtarget; + +import java.util.List; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; + +/** + * @author Ben Zegveld + */ +public class BeforeWithTarget { + private BeforeWithTarget() { + } + + @BeforeMapping + public static void doNothingBeforeWithTarget(Iterable source, @MappingTarget List target) { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Animal.java new file mode 100644 index 0000000000..3d60eac9fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Animal.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.ap.test.bugs._2825; + +/** + * @author orange add + */ +public class Animal { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Cat.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Cat.java new file mode 100644 index 0000000000..ec826c0ffb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Cat.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.ap.test.bugs._2825; + +/** + * @author orange add + */ +public class Cat extends Animal { + private String race; + + public String getRace() { + return race; + } + + public void setRace(String race) { + this.race = race; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Dog.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Dog.java new file mode 100644 index 0000000000..53b41a98c9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Dog.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.ap.test.bugs._2825; + +/** + * @author orange add + */ +public class Dog extends Animal { + private String race; + + public String getRace() { + return race; + } + + public void setRace(String race) { + this.race = race; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Mapper.java new file mode 100644 index 0000000000..c515011b0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Mapper.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.ap.test.bugs._2825; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author orange add + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface Issue2825Mapper { + + Issue2825Mapper INSTANCE = Mappers.getMapper( Issue2825Mapper.class ); + + @SubclassMapping(target = TargetAnimal.class, source = Dog.class) + @SubclassMapping(target = TargetAnimal.class, source = Cat.class) + TargetAnimal map(Animal source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Test.java new file mode 100644 index 0000000000..9c0609b754 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/Issue2825Test.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.test.bugs._2825; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author orange add + */ +@IssueKey("2825") +@WithClasses({ + Animal.class, + Cat.class, + Dog.class, + Issue2825Mapper.class, + TargetAnimal.class, +}) +public class Issue2825Test { + + @ProcessorTest + public void mappingMethodShouldNotBeReusedForSubclassMappings() { + Dog dog = new Dog(); + dog.setName( "Lucky" ); + dog.setRace( "Shepherd" ); + TargetAnimal target = Issue2825Mapper.INSTANCE.map( dog ); + assertThat( target.getName() ).isEqualTo( "Lucky" ); + assertThat( target.getRace() ).isEqualTo( "Shepherd" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/TargetAnimal.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/TargetAnimal.java new file mode 100644 index 0000000000..479741099a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2825/TargetAnimal.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.ap.test.bugs._2825; + +/** + * @author orange add + */ +public class TargetAnimal { + private String name; + + private String race; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getRace() { + return race; + } + + public void setRace(String race) { + this.race = race; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Car.java new file mode 100644 index 0000000000..9419223a42 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Car.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.test.bugs._2839; + +import java.util.List; + +/** + * @author Hakan Özkan + */ +public final class Car { + + private final Id id; + private final List seatIds; + private final List tireIds; + + public Car(Id id, List seatIds, List tireIds) { + this.id = id; + this.seatIds = seatIds; + this.tireIds = tireIds; + } + + public Id getId() { + return id; + } + + public List getSeatIds() { + return seatIds; + } + + public List getTireIds() { + return tireIds; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarDto.java new file mode 100644 index 0000000000..68741ebc92 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarDto.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.test.bugs._2839; + +import java.util.List; + +/** + * @author Hakan Özkan + */ +public final class CarDto { + + private final String id; + private final List seatIds; + private final List tireIds; + + public CarDto(String id, List seatIds, List tireIds) { + this.id = id; + this.seatIds = seatIds; + this.tireIds = tireIds; + } + + public String getId() { + return id; + } + + public List getSeatIds() { + return seatIds; + } + + public List getTireIds() { + return tireIds; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarMapper.java new file mode 100644 index 0000000000..e535935d21 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/CarMapper.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.ap.test.bugs._2839; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Hakan Özkan + */ +@Mapper +public abstract class CarMapper { + + public static final CarMapper MAPPER = Mappers.getMapper( CarMapper.class ); + + public abstract Car toEntity(CarDto dto); + + protected Id mapId(String id) throws Issue2839Exception { + throw new Issue2839Exception("For id " + id); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Id.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Id.java new file mode 100644 index 0000000000..5bb9a29dd7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Id.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.ap.test.bugs._2839; + +import java.util.UUID; + +/** + * @author Hakan Özkan + */ +public class Id { + + private final UUID id; + + public Id() { + this.id = UUID.randomUUID(); + } + + public Id(UUID id) { + this.id = id; + } + + public UUID getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Exception.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Exception.java new file mode 100644 index 0000000000..91f02014d6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Exception.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._2839; + +/** + * @author Hakan Özkan + */ +public class Issue2839Exception extends Exception { + + public Issue2839Exception(String message) { + super( message ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Test.java new file mode 100644 index 0000000000..ed61c39a63 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2839/Issue2839Test.java @@ -0,0 +1,56 @@ +/* + * 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.test.bugs._2839; + +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Hakan Özkan + */ +@IssueKey("2839") +@WithClasses({ + Car.class, + CarDto.class, + CarMapper.class, + Id.class, + Issue2839Exception.class, +}) +public class Issue2839Test { + + @ProcessorTest + void shouldCompile() { + CarDto car1 = new CarDto( + "carId", + Collections.singletonList( "seatId" ), + Collections.singletonList( "tireId" ) + ); + assertThatThrownBy( () -> CarMapper.MAPPER.toEntity( car1 ) ) + .isExactlyInstanceOf( RuntimeException.class ) + .getCause() + .isInstanceOf( Issue2839Exception.class ) + .hasMessage( "For id seatId" ); + + CarDto car2 = new CarDto( "carId", Collections.emptyList(), Collections.singletonList( "tireId" ) ); + assertThatThrownBy( () -> CarMapper.MAPPER.toEntity( car2 ) ) + .isExactlyInstanceOf( RuntimeException.class ) + .getCause() + .isInstanceOf( Issue2839Exception.class ) + .hasMessage( "For id tireId" ); + + CarDto car3 = new CarDto( "carId", Collections.emptyList(), Collections.emptyList() ); + assertThatThrownBy( () -> CarMapper.MAPPER.toEntity( car3 ) ) + .isExactlyInstanceOf( RuntimeException.class ) + .getCause() + .isInstanceOf( Issue2839Exception.class ) + .hasMessage( "For id carId" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java new file mode 100644 index 0000000000..d2baac8cac --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Mapper.java @@ -0,0 +1,51 @@ +/* + * 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.test.bugs._2840; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2840Mapper { + + Issue2840Mapper INSTANCE = + Mappers.getMapper( Issue2840Mapper.class ); + + Issue2840Mapper.Target map(Short shortValue, Integer intValue); + + default int toInt(Number number) { + return number.intValue() + 5; + } + + default short toShort(Number number) { + return (short) (number.shortValue() + 10); + } + + class Target { + + private int intValue; + private short shortValue; + + public int getIntValue() { + return intValue; + } + + public void setIntValue(int intValue) { + this.intValue = intValue; + } + + public short getShortValue() { + return shortValue; + } + + public void setShortValue(short shortValue) { + this.shortValue = shortValue; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.java new file mode 100644 index 0000000000..1c6b19306f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2840/Issue2840Test.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.ap.test.bugs._2840; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2840") +@WithClasses({ + Issue2840Mapper.class, +}) +class Issue2840Test { + + @ProcessorTest + void shouldUseMethodWithMostSpecificReturnType() { + Issue2840Mapper.Target target = Issue2840Mapper.INSTANCE.map( (short) 10, 50 ); + + assertThat( target.getShortValue() ).isEqualTo( (short) 20 ); + assertThat( target.getIntValue() ).isEqualTo( 55 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867BaseMapper.java new file mode 100644 index 0000000000..8ebe1aa98d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867BaseMapper.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.ap.test.bugs._2867; + +import org.mapstruct.MappingTarget; + +/** + * @author Filip Hrisafov + */ +public interface Issue2867BaseMapper { + + void update(@MappingTarget T target, S source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Mapper.java new file mode 100644 index 0000000000..77dfda0fc9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Mapper.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.ap.test.bugs._2867; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2867Mapper extends Issue2867BaseMapper { + + class Target { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class Source { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Test.java new file mode 100644 index 0000000000..5f0e7ad763 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2867/Issue2867Test.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.ap.test.bugs._2867; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2867BaseMapper.class, + Issue2867Mapper.class, +}) +@IssueKey("2867") +class Issue2867Test { + + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = @Diagnostic( + type = Issue2867Mapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 14, + message = "Unmapped target property: \"name\"." + + " Occured at 'void update(T target, S source)' in 'Issue2867BaseMapper'." + ) + ) + @ProcessorTest + void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Mapper.java new file mode 100644 index 0000000000..218f6d384b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Mapper.java @@ -0,0 +1,14 @@ +/* + * 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.test.bugs._2880; + +import org.mapstruct.Mapper; + +@Mapper +public interface Issue2880Mapper { + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Test.java new file mode 100644 index 0000000000..e3d0f810c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Issue2880Test.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.ap.test.bugs._2880; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("2880") +@WithClasses({ + Issue2880Mapper.class, + Outer.class, + Source.class, + Target.class, + TargetData.class +}) +public class Issue2880Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Outer.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Outer.java new file mode 100644 index 0000000000..90b756137a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Outer.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._2880; + +public class Outer { + + public static class SourceData { + + // CHECKSTYLE:OFF + public String value; + // CHECKSTYLE:ON + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Source.java new file mode 100644 index 0000000000..0ab3b81661 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/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.ap.test.bugs._2880; + +import java.util.List; + +public class Source { + + // CHECKSTYLE:OFF + public Outer.SourceData[] data1; + + public Outer.SourceData[] data2; + + public List data3; + + public List data4; + // CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Target.java new file mode 100644 index 0000000000..28c6b1cca1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/Target.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.ap.test.bugs._2880; + +import java.util.List; + +public class Target { + + // CHECKSTYLE:OFF + public TargetData[] data1; + + public List data2; + + public TargetData[] data3; + + public List data4; + // CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/TargetData.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/TargetData.java new file mode 100644 index 0000000000..28cca96601 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2880/TargetData.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.ap.test.bugs._2880; + +public class TargetData { + + // CHECKSTYLE:OFF + public String value; + // CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Mapper.java index 535465b30c..29e6305187 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Mapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._289; @@ -35,7 +22,4 @@ public interface Issue289Mapper { TargetElement sourceElementToTargetElement(SourceElement source); - - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Test.java index 0a6d9ac1c3..4b95513fe2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Test.java @@ -1,29 +1,15 @@ -/** - * Copyright 2012-2015 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.bugs._289; -import static org.fest.assertions.Assertions.assertThat; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/289. @@ -39,11 +25,9 @@ SourceElement.class, TargetElement.class } ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue289Test { - - @Test + @ProcessorTest public void shouldLeaveEmptyTargetSetWhenSourceIsNullAndGetterOnlyForCreateMethod() { Source source = new Source(); @@ -54,7 +38,7 @@ public void shouldLeaveEmptyTargetSetWhenSourceIsNullAndGetterOnlyForCreateMetho assertThat( target.getCollection() ).isEmpty(); } - @Test + @ProcessorTest public void shouldLeaveEmptyTargetSetWhenSourceIsNullAndGetterOnlyForUpdateMethod() { Source source = new Source(); @@ -67,8 +51,7 @@ public void shouldLeaveEmptyTargetSetWhenSourceIsNullAndGetterOnlyForUpdateMetho assertThat( target.getCollection() ).isEmpty(); } - - @Test + @ProcessorTest public void shouldLeaveNullTargetSetWhenSourceIsNullForCreateMethod() { Source source = new Source(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Source.java index 72138d9fb0..76365a6354 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._289; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/SourceElement.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/SourceElement.java index 7414818a0d..453933e940 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/SourceElement.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/SourceElement.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._289; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetElement.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetElement.java index e7925a6cfe..3131fffdf0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetElement.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetElement.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._289; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetWithSetter.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetWithSetter.java index 7f73819103..6c9cdc7fd5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetWithSetter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetWithSetter.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._289; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetWithoutSetter.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetWithoutSetter.java index 398488d18d..c550662e83 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetWithoutSetter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/TargetWithoutSetter.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._289; @@ -27,7 +14,7 @@ */ public class TargetWithoutSetter { - private final Collection collection = new ArrayList(); + private final Collection collection = new ArrayList<>(); public Collection getCollection() { return collection; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Mapper.java new file mode 100644 index 0000000000..205978aeb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Mapper.java @@ -0,0 +1,76 @@ +/* + * 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.test.bugs._2891; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; + +/** + * @author Sergei Portnov + */ +@Mapper +public interface Issue2891Mapper { + + @BeanMapping(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) + @SubclassMapping(source = Source1.class, target = Target1.class) + @SubclassMapping(source = Source2.class, target = Target2.class) + AbstractTarget map(AbstractSource source); + + abstract class AbstractTarget { + + private final String name; + + protected AbstractTarget(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class Target1 extends AbstractTarget { + + protected Target1(String name) { + super( name ); + } + } + + class Target2 extends AbstractTarget { + + protected Target2(String name) { + super( name ); + } + } + + abstract class AbstractSource { + + private final String name; + + protected AbstractSource(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class Source1 extends AbstractSource { + protected Source1(String name) { + super( name ); + } + } + + class Source2 extends AbstractSource { + + protected Source2(String name) { + super( name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Test.java new file mode 100644 index 0000000000..c82d5aa63e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2891/Issue2891Test.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.test.bugs._2891; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Sergei Portnov + */ +@WithClasses({ + Issue2891Mapper.class +}) +@IssueKey("2891") +class Issue2891Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( Issue2891Mapper.class ); + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Mapper.java new file mode 100644 index 0000000000..e13f2083ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Mapper.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.ap.test.bugs._2897; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.bugs._2897.util.Util; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(imports = Util.Factory.class) +public interface Issue2897Mapper { + + Issue2897Mapper INSTANCE = Mappers.getMapper( Issue2897Mapper.class ); + + @Mapping( target = "value", expression = "java(Factory.parse( source ))") + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Test.java new file mode 100644 index 0000000000..718ece4a90 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Issue2897Test.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.ap.test.bugs._2897; + +import org.mapstruct.ap.test.bugs._2897.util.Util; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2897") +@WithClasses({ + Util.class, + Issue2897Mapper.class, + Source.class, + Target.class, +}) +class Issue2897Test { + + @ProcessorTest + void shouldImportNestedClassInMapperImports() { + Target target = Issue2897Mapper.INSTANCE.map( new Source( "test" ) ); + + assertThat( target.getValue() ).isEqualTo( "parsed(test)" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Source.java new file mode 100644 index 0000000000..5d46ad1ba3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Source.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.ap.test.bugs._2897; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Target.java new file mode 100644 index 0000000000..71fbabbe57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/Target.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.ap.test.bugs._2897; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/util/Util.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/util/Util.java new file mode 100644 index 0000000000..5ef0c7e254 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2897/util/Util.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.ap.test.bugs._2897.util; + +import org.mapstruct.ap.test.bugs._2897.Source; + +/** + * @author Filip Hrisafov + */ +public class Util { + + public static class Factory { + + public static String parse(Source source) { + return source == null ? null : "parsed(" + source.getValue() + ")"; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/ConditionWithTargetTypeOnCollectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/ConditionWithTargetTypeOnCollectionMapper.java new file mode 100644 index 0000000000..7e833cfc53 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/ConditionWithTargetTypeOnCollectionMapper.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.ap.test.bugs._2901; + +import java.util.List; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; + +@Mapper +public interface ConditionWithTargetTypeOnCollectionMapper { + + Target map(Source source); + + @Condition + default boolean check(List test, @TargetType Class type) { + return type.isInstance( test ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Issue2901Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Issue2901Test.java new file mode 100644 index 0000000000..3ae2c3db86 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Issue2901Test.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.ap.test.bugs._2901; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Ben Zegveld + */ +@IssueKey( "2901" ) +class Issue2901Test { + + @ProcessorTest + @WithClasses( { Source.class, Target.class, ConditionWithTargetTypeOnCollectionMapper.class } ) + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Source.java new file mode 100644 index 0000000000..4eb50d58fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/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.ap.test.bugs._2901; + +import java.util.List; + +public class Source { + + private List field; + + public List getField() { + return field; + } + + public void setField(List field) { + this.field = field; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Target.java new file mode 100644 index 0000000000..b4c5299f35 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2901/Target.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.ap.test.bugs._2901; + +import java.util.List; + +public class Target { + + private List field; + + public List getField() { + return field; + } + + public void setField(List field) { + this.field = field; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Issue2907Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Issue2907Test.java new file mode 100644 index 0000000000..a005b6a1ad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Issue2907Test.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.test.bugs._2907; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.bugs._2907.mapper.Issue2907Mapper; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2907") +@WithClasses({ + Issue2907Mapper.class, + Source.class, + SourceNested.class, + Target.class, +}) +class Issue2907Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void shouldNotGeneratedImportForNestedClass() { + generatedSource.forMapper( Issue2907Mapper.class ) + .containsNoImportFor( Target.TargetNested.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Source.java new file mode 100644 index 0000000000..42d015fde7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/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.ap.test.bugs._2907; + +import java.util.Set; + +public class Source { + + private Set nested; + + public Set getNested() { + return nested; + } + + public void setNested(Set nested) { + this.nested = nested; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/SourceNested.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/SourceNested.java new file mode 100644 index 0000000000..a58a94f0f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/SourceNested.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.ap.test.bugs._2907; + +public class SourceNested { + + private String prop; + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/Target.java new file mode 100644 index 0000000000..80a796d20d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/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.ap.test.bugs._2907; + +public class Target { + + private TargetNested[] nested; + + public TargetNested[] getNested() { + return nested; + } + + public void setNested(TargetNested[] nested) { + this.nested = nested; + } + + public static class TargetNested { + private String prop; + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/mapper/Issue2907Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/mapper/Issue2907Mapper.java new file mode 100644 index 0000000000..5244f95bb4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2907/mapper/Issue2907Mapper.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.ap.test.bugs._2907.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2907.Source; +import org.mapstruct.ap.test.bugs._2907.Target; + +@Mapper +public interface Issue2907Mapper { + + Target map(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java new file mode 100644 index 0000000000..095f5457fd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Mapper.java @@ -0,0 +1,85 @@ +/* + * 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.test.bugs._2913; + +import java.math.BigDecimal; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2913Mapper { + + Issue2913Mapper INSTANCE = Mappers.getMapper( Issue2913Mapper.class ); + + @Mapping(target = "doublePrimitiveValue", source = "rounding") + @Mapping(target = "doubleValue", source = "rounding") + @Mapping(target = "longPrimitiveValue", source = "rounding") + @Mapping(target = "longValue", source = "rounding") + Target map(Source source); + + default Long mapAmount(BigDecimal amount) { + return amount != null ? amount.movePointRight( 2 ).longValue() : null; + } + + class Target { + + private double doublePrimitiveValue; + private Double doubleValue; + private long longPrimitiveValue; + private Long longValue; + + public double getDoublePrimitiveValue() { + return doublePrimitiveValue; + } + + public void setDoublePrimitiveValue(double doublePrimitiveValue) { + this.doublePrimitiveValue = doublePrimitiveValue; + } + + public Double getDoubleValue() { + return doubleValue; + } + + public void setDoubleValue(Double doubleValue) { + this.doubleValue = doubleValue; + } + + public long getLongPrimitiveValue() { + return longPrimitiveValue; + } + + public void setLongPrimitiveValue(long longPrimitiveValue) { + this.longPrimitiveValue = longPrimitiveValue; + } + + public Long getLongValue() { + return longValue; + } + + public void setLongValue(Long longValue) { + this.longValue = longValue; + } + } + + class Source { + + private final BigDecimal rounding; + + public Source(BigDecimal rounding) { + this.rounding = rounding; + } + + public BigDecimal getRounding() { + return rounding; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.java new file mode 100644 index 0000000000..7257c9ab7e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2913/Issue2913Test.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.test.bugs._2913; + +import java.math.BigDecimal; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2913") +@WithClasses({ + Issue2913Mapper.class, +}) +class Issue2913Test { + + @ProcessorTest + void shouldNotWidenWithUserDefinedMethods() { + Issue2913Mapper.Source source = new Issue2913Mapper.Source( BigDecimal.valueOf( 10.543 ) ); + Issue2913Mapper.Target target = Issue2913Mapper.INSTANCE.map( source ); + + assertThat( target.getDoubleValue() ).isEqualTo( 10.543 ); + assertThat( target.getDoublePrimitiveValue() ).isEqualTo( 10.543 ); + assertThat( target.getLongValue() ).isEqualTo( 1054 ); + assertThat( target.getLongPrimitiveValue() ).isEqualTo( 1054 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.java new file mode 100644 index 0000000000..d686fcbc16 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Mapper.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.ap.test.bugs._2921; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2921Mapper { + + Issue2921Mapper INSTANCE = Mappers.getMapper( Issue2921Mapper.class ); + + Target map(Source source); + + default Short toShort(Integer value) { + throw new UnsupportedOperationException( "toShort method should not be used" ); + } + + class Source { + private final Integer value; + + public Source(Integer value) { + this.value = value; + } + + public Integer getValue() { + return value; + } + } + + class Target { + private final int value; + + public Target(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.java new file mode 100644 index 0000000000..5b8dd0386c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2921/Issue2921Test.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.ap.test.bugs._2921; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2921") +@WithClasses({ + Issue2921Mapper.class, +}) +class Issue2921Test { + + @ProcessorTest + void shouldNotUseIntegerToShortForMappingIntegerToInt() { + Issue2921Mapper.Target target = Issue2921Mapper.INSTANCE.map( new Issue2921Mapper.Source( 10 ) ); + assertThat( target.getValue() ).isEqualTo( 10 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Mapper.java new file mode 100644 index 0000000000..d231413820 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Mapper.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.ap.test.bugs._2925; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue2925Mapper { + + Issue2925Mapper INSTANCE = Mappers.getMapper( Issue2925Mapper.class ); + + Target map(Source source); + + static Optional toOptional(T value) { + return Optional.ofNullable( value ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Test.java new file mode 100644 index 0000000000..73d009d093 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Issue2925Test.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._2925; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2925") +@WithClasses({ + Issue2925Mapper.class, + Source.class, + Target.class, +}) +class Issue2925Test { + + @ProcessorTest + void shouldUseOptionalWrappingMethod() { + Target target = Issue2925Mapper.INSTANCE.map( new Source( 10L ) ); + + assertThat( target.getValue() ) + .hasValue( 10L ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Source.java new file mode 100644 index 0000000000..c21ce67775 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Source.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.ap.test.bugs._2925; + +public class Source { + + private final long value; + + public Source(long value) { + this.value = value; + } + + public long getValue() { + return value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Target.java new file mode 100644 index 0000000000..4693a83fdb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2925/Target.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.ap.test.bugs._2925; + +import java.util.Optional; + +public class Target { + + private Long value; + + public Optional getValue() { + return Optional.ofNullable( value ); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public void setValue(Optional value) { + this.value = value.orElse( null ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Mapper.java new file mode 100644 index 0000000000..95d767acb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Mapper.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.ap.test.bugs._2937; + +import java.util.ArrayList; +import java.util.Collection; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue2937Mapper { + + Issue2937Mapper INSTANCE = Mappers.getMapper( Issue2937Mapper.class ); + + Target map(Source source); + + @Condition + default boolean isApplicable(Collection collection) { + return collection == null || collection.size() > 1; + } + + class Source { + private final Collection names; + + public Source(Collection names) { + this.names = names; + } + + public Collection getNames() { + return names; + } + + } + + class Target { + private final Collection names; + + public Target() { + this.names = new ArrayList<>(); + } + + public Collection getNames() { + return names; + } + + public void addName(String name) { + this.names.add( name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Test.java new file mode 100644 index 0000000000..140575263c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2937/Issue2937Test.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.ap.test.bugs._2937; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2937") +@WithClasses({ + Issue2937Mapper.class, +}) +class Issue2937Test { + + @ProcessorTest + void shouldCorrectlyUseConditionalForAdder() { + List sourceNames = new ArrayList<>(); + sourceNames.add( "Tester 1" ); + Issue2937Mapper.Source source = new Issue2937Mapper.Source( sourceNames ); + Issue2937Mapper.Target target = Issue2937Mapper.INSTANCE.map( source ); + + assertThat( target.getNames() ).isEmpty(); + + sourceNames.add( "Tester 2" ); + + target = Issue2937Mapper.INSTANCE.map( source ); + + assertThat( target.getNames() ) + .containsExactly( "Tester 1", "Tester 2" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Mapper.java new file mode 100644 index 0000000000..604d546273 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Mapper.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.ap.test.bugs._2945; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._2945._target.Target; +import org.mapstruct.ap.test.bugs._2945.source.Source; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue2945Mapper { + + Issue2945Mapper INSTANCE = Mappers.getMapper( Issue2945Mapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Test.java new file mode 100644 index 0000000000..11dcf752c0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/Issue2945Test.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.test.bugs._2945; + +import org.mapstruct.ap.test.bugs._2945._target.EnumHolder; +import org.mapstruct.ap.test.bugs._2945._target.Target; +import org.mapstruct.ap.test.bugs._2945.source.Source; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2945") +@WithClasses({ + EnumHolder.class, + Issue2945Mapper.class, + Source.class, + Target.class, +}) +class Issue2945Test { + + @ProcessorTest + void shouldCompile() { + Target target = Issue2945Mapper.INSTANCE.map( new Source( "VALUE_1" ) ); + + assertThat( target.getProperty() ).isEqualTo( EnumHolder.Property.VALUE_1 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/EnumHolder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/EnumHolder.java new file mode 100644 index 0000000000..0b75e9ce36 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/EnumHolder.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.ap.test.bugs._2945._target; + +public class EnumHolder { + public enum Property { + VALUE_1, + VALUE_2; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/Target.java new file mode 100644 index 0000000000..25b69eed29 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/_target/Target.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.ap.test.bugs._2945._target; + +public class Target { + private EnumHolder.Property property; + + public EnumHolder.Property getProperty() { + return property; + } + + public void setProperty(EnumHolder.Property property) { + this.property = property; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/source/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/source/Source.java new file mode 100644 index 0000000000..8364941ad1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2945/source/Source.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.ap.test.bugs._2945.source; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final String property; + + public Source(String property) { + this.property = property; + } + + public String getProperty() { + return property; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Mapper.java new file mode 100644 index 0000000000..553563066e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Mapper.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.ap.test.bugs._2949; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue2949Mapper { + + Issue2949Mapper INSTANCE = Mappers.getMapper( Issue2949Mapper.class ); + + @Mapping( target = "property1", ignore = true) + @InheritInverseConfiguration + Source toSource(Target target); + + @BeanMapping(ignoreUnmappedSourceProperties = { "property1" }) + Target toTarget(Source source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + private final String value; + private final String property1; + + public Source(String value, String property1) { + this.value = value; + this.property1 = property1; + } + + public String getValue() { + return value; + } + + public String getProperty1() { + return property1; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Test.java new file mode 100644 index 0000000000..2a277a4bfd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2949/Issue2949Test.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.test.bugs._2949; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue2949Mapper.class +}) +class Issue2949Test { + + @ProcessorTest + void shouldCorrectlyInheritInverseBeanMappingWithIgnoreUnmappedSourceProeprties() { + Issue2949Mapper.Target target = Issue2949Mapper.INSTANCE.toTarget( new Issue2949Mapper.Source( + "test", + "first" + ) ); + + assertThat( target.getValue() ).isEqualTo( "test" ); + + Issue2949Mapper.Source source = Issue2949Mapper.INSTANCE.toSource( target ); + + assertThat( source.getValue() ).isEqualTo( "test" ); + assertThat( source.getProperty1() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.java new file mode 100644 index 0000000000..885d74448f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Mapper.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.ap.test.bugs._2952; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE) +public interface Issue2952Mapper { + + Issue2952Mapper INSTANCE = Mappers.getMapper( Issue2952Mapper.class ); + + Target map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + private final Map attributes = new HashMap<>(); + private final List values = new ArrayList<>(); + private String value; + + public Map getAttributes() { + return Collections.unmodifiableMap( attributes ); + } + + public List getValues() { + return Collections.unmodifiableList( values ); + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.java new file mode 100644 index 0000000000..7a517ec979 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_2952/Issue2952Test.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.ap.test.bugs._2952; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2952") +@WithClasses({ + Issue2952Mapper.class +}) +class Issue2952Test { + + @ProcessorTest + void shouldCorrectIgnoreImmutableIterable() { + Issue2952Mapper.Target target = Issue2952Mapper.INSTANCE.map( new Issue2952Mapper.Source( "test" ) ); + + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java new file mode 100644 index 0000000000..1683db8f39 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Mapper.java @@ -0,0 +1,153 @@ +/* + * 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.test.bugs._3015; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; + +/** + * @author orange add + */ +@Mapper +public interface Issue3015Mapper { + + @AnnotateWith( CustomMethodOnlyAnnotation.class ) + Target map(Source source); + + class Source { + + private NestedSource nested; + private List list; + private Stream stream; + private AnnotateSourceEnum annotateWithEnum; + private Map map; + + public NestedSource getNested() { + return nested; + } + + public void setNested(NestedSource nested) { + this.nested = nested; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public Stream getStream() { + return stream; + } + + public void setStream(Stream stream) { + this.stream = stream; + } + + public AnnotateSourceEnum getAnnotateWithEnum() { + return annotateWithEnum; + } + + public void setAnnotateWithEnum(AnnotateSourceEnum annotateWithEnum) { + this.annotateWithEnum = annotateWithEnum; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } + + class Target { + private NestedTarget nested; + private List list; + private Stream stream; + private AnnotateTargetEnum annotateWithEnum; + private Map map; + + public NestedTarget getNested() { + return nested; + } + + public void setNested(NestedTarget nested) { + this.nested = nested; + } + + public List getList() { + return list; + } + + public void setList(List list) { + this.list = list; + } + + public Stream getStream() { + return stream; + } + + public void setStream(Stream stream) { + this.stream = stream; + } + + public AnnotateTargetEnum getAnnotateWithEnum() { + return annotateWithEnum; + } + + public void setAnnotateWithEnum(AnnotateTargetEnum annotateWithEnum) { + this.annotateWithEnum = annotateWithEnum; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } + + enum AnnotateSourceEnum { + EXISTING; + } + + enum AnnotateTargetEnum { + EXISTING; + } + + class NestedSource { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class NestedTarget { + private Integer value; + + public Integer getValue() { + return value; + } + + public void setValue(Integer value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.java new file mode 100644 index 0000000000..96004b8c2d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3015/Issue3015Test.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.test.bugs._3015; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.ap.test.annotatewith.CustomMethodOnlyAnnotation; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author orange add + */ +@WithClasses({ + Issue3015Mapper.class, + CustomMethodOnlyAnnotation.class +}) +class Issue3015Test { + + @ProcessorTest + void noNeedPassAnnotationToForgeMethod() { + Issue3015Mapper mapper = Mappers.getMapper( Issue3015Mapper.class ); + Method[] declaredMethods = mapper.getClass().getDeclaredMethods(); + List annotationMethods = Arrays.stream( declaredMethods ) + .filter( method -> method.getAnnotation( CustomMethodOnlyAnnotation.class ) != null ) + .collect( Collectors.toList() ); + assertThat( annotationMethods ).hasSize( 1 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.java new file mode 100644 index 0000000000..d8c444d8c5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057Mapper.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.test.bugs._3057; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface Issue3057Mapper { + + Issue3057Mapper INSTANCE = Mappers.getMapper( Issue3057Mapper.class ); + + class Source { + private Source self; + + public Source getSelf() { + return self; + } + + public void setSelf(Source self) { + this.self = self; + } + } + + class Target { + private Target self; + private String value; + + public Target getSelf() { + return self; + } + + public void setSelf(Target self) { + this.self = self; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + @Mapping( target = "value", constant = "constantValue" ) + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.java new file mode 100644 index 0000000000..7e64f529f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3057/Issue3057MapperTest.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.test.bugs._3057; + +import org.mapstruct.ap.test.bugs._3057.Issue3057Mapper.Source; +import org.mapstruct.ap.test.bugs._3057.Issue3057Mapper.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ben Zegveld + */ +@WithClasses(Issue3057Mapper.class) +@IssueKey("3057") +class Issue3057MapperTest { + + @ProcessorTest + void mapsSelf() { + Source sourceOuter = new Issue3057Mapper.Source(); + Source sourceInner = new Issue3057Mapper.Source(); + sourceOuter.setSelf( sourceInner ); + + Target targetOuter = Issue3057Mapper.INSTANCE.map( sourceOuter ); + + assertThat( targetOuter.getValue() ).isEqualTo( "constantValue" ); + assertThat( targetOuter.getSelf().getValue() ).isEqualTo( "constantValue" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Mapper.java index 394c3bf793..9d0b9a2574 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Mapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._306; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Test.java index 5499b5012e..5d4f0c84b2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Test.java @@ -1,28 +1,13 @@ -/** - * Copyright 2012-2015 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.bugs._306; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/306. @@ -30,10 +15,9 @@ * @author Sjaak Derksen */ @IssueKey( "306" ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue306Test { - @Test + @ProcessorTest @WithClasses( { Issue306Mapper.class, Source.class, Target.class } ) public void shouldForgeNewIterableMappingMethod() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Source.java index 1c0c5623ae..741e54fe3b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Source.java @@ -1,26 +1,12 @@ -/** - * Copyright 2012-2015 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._306; import java.util.Set; - public class Source { private Set fooSet; @@ -32,5 +18,4 @@ public Set getFooSet() { public void setFooSet(Set fooSet) { this.fooSet = fooSet; } - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Target.java index fce8ae9670..50196032b5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._306; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java new file mode 100644 index 0000000000..f1a630a444 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077Mapper.java @@ -0,0 +1,65 @@ +/* + * 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.test.bugs._3077; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3077Mapper { + + Issue3077Mapper INSTANCE = Mappers.getMapper( Issue3077Mapper.class ); + + class Source { + private final String source; + private final Source self; + + public Source(String source, Source self) { + this.source = source; + this.self = self; + } + + public String getSource() { + return source; + } + + public Source getSelf() { + return self; + } + } + + class Target { + private String value; + private Target self; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Target getSelf() { + return self; + } + + public void setSelf(Target self) { + this.self = self; + } + + } + + @Named("self") + @Mapping(target = "value", source = "source") + @Mapping(target = "self", qualifiedByName = "self") + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.java new file mode 100644 index 0000000000..bfba7988aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3077/Issue3077MapperTest.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.ap.test.bugs._3077; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3077Mapper.class) +@IssueKey("3057") +class Issue3077MapperTest { + + @ProcessorTest + void mapsSelf() { + Issue3077Mapper.Source sourceInner = new Issue3077Mapper.Source( "inner", null ); + Issue3077Mapper.Source sourceOuter = new Issue3077Mapper.Source( "outer", sourceInner ); + + Issue3077Mapper.Target targetOuter = Issue3077Mapper.INSTANCE.map( sourceOuter ); + + assertThat( targetOuter.getValue() ).isEqualTo( "outer" ); + assertThat( targetOuter.getSelf().getValue() ).isEqualTo( "inner" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089BuilderProvider.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089BuilderProvider.java new file mode 100644 index 0000000000..52f0f96345 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089BuilderProvider.java @@ -0,0 +1,96 @@ +/* + * 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.test.bugs._3089; + +import java.util.List; +import java.util.Objects; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +/** + * @author Oliver Erhart + */ +public class Issue3089BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider { + + @Override + protected BuilderInfo findBuilderInfo(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.toString().endsWith( ".Item" ) ) { + BuilderInfo info = findBuilderInfoFromInnerBuilderClass( typeElement ); + if ( info != null ) { + return info; + } + } + return super.findBuilderInfo( typeElement ); + } + + /** + * Looks for inner builder class in the Immutable interface / abstract class. + * + * The inner builder class should be be declared with the following line + * + *

      +     *     public static Builder() extends ImmutableItem.Builder { }
      +     * 
      + * + * The Immutable instance should be created with the following line + * + *
      +     *     new Item.Builder().withId("123").build();
      +     * 
      + * + * @see org.mapstruct.ap.test.bugs._3089.domain.Item + * + * @param typeElement + * @return + */ + private BuilderInfo findBuilderInfoFromInnerBuilderClass(TypeElement typeElement) { + if ( shouldIgnore( typeElement ) ) { + return null; + } + + List innerTypes = ElementFilter.typesIn( typeElement.getEnclosedElements() ); + ExecutableElement defaultConstructor = innerTypes.stream() + .filter( this::isBuilderCandidate ) + .map( this::getEmptyArgPublicConstructor ) + .filter( Objects::nonNull ) + .findAny() + .orElse( null ); + + if ( defaultConstructor != null ) { + return new BuilderInfo.Builder() + .builderCreationMethod( defaultConstructor ) + .buildMethod( findBuildMethods( (TypeElement) defaultConstructor.getEnclosingElement(), typeElement ) ) + .build(); + } + return null; + } + + private boolean isBuilderCandidate(TypeElement innerType ) { + TypeElement outerType = (TypeElement) innerType.getEnclosingElement(); + String packageName = this.elementUtils.getPackageOf( outerType ).getQualifiedName().toString(); + Name outerSimpleName = outerType.getSimpleName(); + String builderClassName = packageName + ".Immutable" + outerSimpleName + ".Builder"; + return innerType.getSimpleName().contentEquals( "Builder" ) + && getTypeElement( innerType.getSuperclass() ).getQualifiedName().contentEquals( builderClassName ) + && innerType.getModifiers().contains( Modifier.PUBLIC ); + } + + private ExecutableElement getEmptyArgPublicConstructor(TypeElement builderType) { + return ElementFilter.constructorsIn( builderType.getEnclosedElements() ).stream() + .filter( c -> c.getParameters().isEmpty() ) + .filter( c -> c.getModifiers().contains( Modifier.PUBLIC ) ) + .findAny() + .orElse( null ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089Test.java new file mode 100644 index 0000000000..f7c138c146 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/Issue3089Test.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.ap.test.bugs._3089; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.test.bugs._3089.domain.ImmutableItem; +import org.mapstruct.ap.test.bugs._3089.domain.Item; +import org.mapstruct.ap.test.bugs._3089.dto.ImmutableItemDTO; +import org.mapstruct.ap.test.bugs._3089.dto.ItemDTO; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithServiceImplementations; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Oliver Erhart + */ +@WithClasses({ + ItemMapper.class, + Item.class, + ImmutableItem.class, + ItemDTO.class, + ImmutableItemDTO.class +}) +@IssueKey("3089") +@WithServiceImplementations({ + @WithServiceImplementation(provides = BuilderProvider.class, value = Issue3089BuilderProvider.class), + @WithServiceImplementation(provides = AccessorNamingStrategy.class, value = ImmutablesAccessorNamingStrategy.class) +}) +public class Issue3089Test { + + @ProcessorTest + public void shouldIgnorePutterOfMap() { + + Map attributesMap = new HashMap<>(); + attributesMap.put( "a", "b" ); + attributesMap.put( "c", "d" ); + + ItemDTO item = ImmutableItemDTO.builder() + .id( "test" ) + .attributes( attributesMap ) + .build(); + + Item target = ItemMapper.INSTANCE.map( item ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "test" ); + assertThat( target.getAttributes() ).isEqualTo( attributesMap ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/ItemMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/ItemMapper.java new file mode 100644 index 0000000000..ed06115ae2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/ItemMapper.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.ap.test.bugs._3089; + +import org.mapstruct.Builder; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._3089.domain.Item; +import org.mapstruct.ap.test.bugs._3089.dto.ItemDTO; +import org.mapstruct.factory.Mappers; + +/** + * @author Oliver Erhart + */ +@Mapper(builder = @Builder) +public abstract class ItemMapper { + + public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class ); + + public abstract Item map(ItemDTO itemDTO); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/ImmutableItem.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/ImmutableItem.java new file mode 100644 index 0000000000..ab5a6afdc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/ImmutableItem.java @@ -0,0 +1,296 @@ +/* + * 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.test.bugs._3089.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable implementation of {@link Item}. + *

      + * Superclass should expose a static subclass of the Builder to create immutable instance + * {@code public static Builder extends ImmutableItem.Builder}. + * + * @author Oliver Erhart + */ +@SuppressWarnings({"all"}) +public final class ImmutableItem extends Item { + private final String id; + private final Map attributes; + + private ImmutableItem(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * @return The value of the {@code attributes} attribute + */ + @Override + public Map getAttributes() { + return attributes; + } + + /** + * Copy the current immutable object by setting a value for the {@link Item#getId() id} attribute. + * An equals check used to prevent copying of the same value by returning {@code this}. + * @param value A new value for id + * @return A modified copy of the {@code this} object + */ + public final ImmutableItem withId(String value) { + String newValue = Objects.requireNonNull(value, "id"); + if (this.id.equals(newValue)) return this; + return new ImmutableItem(newValue, this.attributes); + } + + /** + * Copy the current immutable object by replacing the {@link Item#getAttributes() attributes} map with the specified map. + * Nulls are not permitted as keys or values. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * @param entries The entries to be added to the attributes map + * @return A modified copy of {@code this} object + */ + public final ImmutableItem withAttributes(Map entries) { + if (this.attributes == entries) return this; + Map newValue = createUnmodifiableMap(true, false, entries); + return new ImmutableItem(this.id, newValue); + } + + /** + * This instance is equal to all instances of {@code ImmutableItem} that have equal attribute values. + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if (this == another) return true; + return another instanceof ImmutableItem + && equalTo((ImmutableItem) another); + } + + private boolean equalTo(ImmutableItem another) { + return id.equals(another.id) + && attributes.equals(another.attributes); + } + + /** + * Computes a hash code from attributes: {@code id}, {@code attributes}. + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += (h << 5) + id.hashCode(); + h += (h << 5) + attributes.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + ", attributes=" + attributes + + "}"; + } + + /** + * Creates an immutable copy of a {@link Item} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItem copyOf(Item instance) { + if (instance instanceof ImmutableItem) { + return (ImmutableItem) instance; + } + return ImmutableItem.builder() + .from(instance) + .build(); + } + + /** + * Creates a builder for {@link ImmutableItem ImmutableItem}. + *

      +   * ImmutableItem.builder()
      +   *    .id(String) // required {@link Item#getId() id}
      +   *    .putAttributes|putAllAttributes(String => String) // {@link Item#getAttributes() attributes} mappings
      +   *    .build();
      +   * 
      + * @return A new ImmutableItem builder + */ + public static ImmutableItem.Builder builder() { + return new ImmutableItem.Builder(); + } + + /** + * Builds instances of type {@link ImmutableItem ImmutableItem}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + private Map attributes = new LinkedHashMap(); + + public Builder() { + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * Collection elements and entries will be added, not replaced. + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final Builder from(Item instance) { + Objects.requireNonNull(instance, "instance"); + id(instance.getId()); + putAllAttributes(instance.getAttributes()); + return this; + } + + /** + * Initializes the value for the {@link Item#getId() id} attribute. + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final Builder id(String id) { + this.id = Objects.requireNonNull(id, "id"); + initBits &= ~INIT_BIT_ID; + return this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. + * @param key The key in the attributes map + * @param value The associated value in the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Builder putAttributes(String key, String value) { + this.attributes.put( + Objects.requireNonNull(key, "attributes key"), + Objects.requireNonNull(value, "attributes value")); + return this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * @param entry The key and value entry + * @return {@code this} builder for use in a chained invocation + */ + public final Builder putAttributes(Map.Entry entry) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull(k, "attributes key"), + Objects.requireNonNull(v, "attributes value")); + return this; + } + + /** + * Sets or replaces all mappings from the specified map as entries for the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * @param entries The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Builder attributes(Map entries) { + this.attributes.clear(); + return putAllAttributes(entries); + } + + /** + * Put all mappings from the specified map as entries to {@link Item#getAttributes() attributes} map. Nulls are not permitted + * @param entries The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Builder putAllAttributes(Map entries) { + for (Map.Entry e : entries.entrySet()) { + String k = e.getKey(); + String v = e.getValue(); + this.attributes.put( + Objects.requireNonNull(k, "attributes key"), + Objects.requireNonNull(v, "attributes value")); + } + return this; + } + + /** + * Builds a new {@link ImmutableItem ImmutableItem}. + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItem build() { + if (initBits != 0) { + throw new IllegalStateException(formatRequiredAttributesMessage()); + } + return new ImmutableItem(id, createUnmodifiableMap(false, false, attributes)); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ((initBits & INIT_BIT_ID) != 0) attributes.add("id"); + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } + + private static Map createUnmodifiableMap(boolean checkNulls, boolean skipNulls, Map map) { + switch (map.size()) { + case 0: return Collections.emptyMap(); + case 1: { + Map.Entry e = map.entrySet().iterator().next(); + K k = e.getKey(); + V v = e.getValue(); + if (checkNulls) { + Objects.requireNonNull(k, "key"); + Objects.requireNonNull(v, "value"); + } + if (skipNulls && (k == null || v == null)) { + return Collections.emptyMap(); + } + return Collections.singletonMap(k, v); + } + default: { + Map linkedMap = new LinkedHashMap<>(map.size()); + if (skipNulls || checkNulls) { + for (Map.Entry e : map.entrySet()) { + K k = e.getKey(); + V v = e.getValue(); + if (skipNulls) { + if (k == null || v == null) continue; + } else if (checkNulls) { + Objects.requireNonNull(k, "key"); + Objects.requireNonNull(v, "value"); + } + linkedMap.put(k, v); + } + } else { + linkedMap.putAll(map); + } + return Collections.unmodifiableMap(linkedMap); + } + } + } +} \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/Item.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/Item.java new file mode 100644 index 0000000000..e193432367 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/domain/Item.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.ap.test.bugs._3089.domain; + +import java.util.Map; + +/** + * @author Oliver Erhart + */ +public abstract class Item { + public abstract String getId(); + + public abstract Map getAttributes(); + + public static class Builder extends ImmutableItem.Builder { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ImmutableItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ImmutableItemDTO.java new file mode 100644 index 0000000000..de64727bf1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ImmutableItemDTO.java @@ -0,0 +1,330 @@ +/* + * 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.test.bugs._3089.dto; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable implementation of {@link ItemDTO}. + *

      + * Use the builder to create immutable instances: + * {@code ImmutableItemDTO.builder()}. + * + * @author Oliver Erhart + */ +public final class ImmutableItemDTO extends ItemDTO { + private final String id; + private final Map attributes; + + private ImmutableItemDTO(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * @return The value of the {@code attributes} attribute + */ + @Override + public Map getAttributes() { + return attributes; + } + + /** + * Copy the current immutable object by setting a value for the {@link ItemDTO#getId() id} attribute. + * An equals check used to prevent copying of the same value by returning {@code this}. + * + * @param value A new value for id + * @return A modified copy of the {@code this} object + */ + public ImmutableItemDTO withId(String value) { + String newValue = Objects.requireNonNull( value, "id" ); + if ( this.id.equals( newValue ) ) { + return this; + } + return new ImmutableItemDTO( newValue, this.attributes ); + } + + /** + * Copy the current immutable object by replacing the {@link ItemDTO#getAttributes() attributes} map with + * the specified map. + * Nulls are not permitted as keys or values. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * + * @param entries The entries to be added to the attributes map + * @return A modified copy of {@code this} object + */ + public ImmutableItemDTO withAttributes(Map entries) { + if ( this.attributes == entries ) { + return this; + } + Map newValue = createUnmodifiableMap( true, false, entries ); + return new ImmutableItemDTO( this.id, newValue ); + } + + /** + * This instance is equal to all instances of {@code ImmutableItemDTO} that have equal attribute values. + * + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if ( this == another ) { + return true; + } + return another instanceof ImmutableItemDTO + && equalTo( (ImmutableItemDTO) another ); + } + + private boolean equalTo(ImmutableItemDTO another) { + return id.equals( another.id ) + && attributes.equals( another.attributes ); + } + + /** + * Computes a hash code from attributes: {@code id}, {@code attributes}. + * + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 5381; + h += ( h << 5 ) + id.hashCode(); + h += ( h << 5 ) + attributes.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + ", attributes=" + attributes + + "}"; + } + + /** + * Creates an immutable copy of a {@link ItemDTO} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItemDTO copyOf(ItemDTO instance) { + if ( instance instanceof ImmutableItemDTO ) { + return (ImmutableItemDTO) instance; + } + return ImmutableItemDTO.builder() + .from( instance ) + .build(); + } + + /** + * Creates a builder for {@link ImmutableItemDTO ImmutableItemDTO}. + *

      +     * ImmutableItemDTO.builder()
      +     *    .id(String) // required {@link ItemDTO#getId() id}
      +     *    .putAttributes|putAllAttributes(String => String) // {@link ItemDTO#getAttributes() attributes} mappings
      +     *    .build();
      +     * 
      + * + * @return A new ImmutableItemDTO builder + */ + public static ImmutableItemDTO.Builder builder() { + return new ImmutableItemDTO.Builder(); + } + + /** + * Builds instances of type {@link ImmutableItemDTO ImmutableItemDTO}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + private Map attributes = new LinkedHashMap(); + + public Builder() { + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * Collection elements and entries will be added, not replaced. + * + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder from(ItemDTO instance) { + Objects.requireNonNull( instance, "instance" ); + id( instance.getId() ); + putAllAttributes( instance.getAttributes() ); + return this; + } + + /** + * Initializes the value for the {@link ItemDTO#getId() id} attribute. + * + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder id(String id) { + this.id = Objects.requireNonNull( id, "id" ); + initBits &= ~INIT_BIT_ID; + return this; + } + + /** + * Put one entry to the {@link ItemDTO#getAttributes() attributes} map. + * + * @param key The key in the attributes map + * @param value The associated value in the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder putAttributes(String key, String value) { + this.attributes.put( + Objects.requireNonNull( key, "attributes key" ), + Objects.requireNonNull( value, "attributes value" ) + ); + return this; + } + + /** + * Put one entry to the {@link ItemDTO#getAttributes() attributes} map. Nulls are not permitted + * + * @param entry The key and value entry + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder putAttributes(Map.Entry entry) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + return this; + } + + /** + * Sets or replaces all mappings from the specified map as entries for the {@link ItemDTO#getAttributes() + * attributes} map. Nulls are not permitted + * + * @param entries The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder attributes(Map entries) { + this.attributes.clear(); + return putAllAttributes( entries ); + } + + /** + * Put all mappings from the specified map as entries to {@link ItemDTO#getAttributes() attributes} map. + * Nulls are not permitted + * + * @param entries The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final ImmutableItemDTO.Builder putAllAttributes(Map entries) { + for ( Map.Entry e : entries.entrySet() ) { + String k = e.getKey(); + String v = e.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + } + return this; + } + + /** + * Builds a new {@link ImmutableItemDTO ImmutableItemDTO}. + * + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItemDTO build() { + if ( initBits != 0 ) { + throw new IllegalStateException( formatRequiredAttributesMessage() ); + } + return new ImmutableItemDTO( id, createUnmodifiableMap( false, false, attributes ) ); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ( ( initBits & INIT_BIT_ID ) != 0 ) { + attributes.add( "id" ); + } + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } + + @SuppressWarnings( "checkstyle:AvoidNestedBlocks" ) + private static Map createUnmodifiableMap(boolean checkNulls, boolean skipNulls, + Map map) { + switch ( map.size() ) { + case 0: + return Collections.emptyMap(); + case 1: { + Map.Entry e = map.entrySet().iterator().next(); + K k = e.getKey(); + V v = e.getValue(); + if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + if ( skipNulls && ( k == null || v == null ) ) { + return Collections.emptyMap(); + } + return Collections.singletonMap( k, v ); + } + default: { + Map linkedMap = new LinkedHashMap<>( map.size() ); + if ( skipNulls || checkNulls ) { + for ( Map.Entry e : map.entrySet() ) { + K k = e.getKey(); + V v = e.getValue(); + if ( skipNulls ) { + if ( k == null || v == null ) { + continue; + } + } + else if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + linkedMap.put( k, v ); + } + } + else { + linkedMap.putAll( map ); + } + return Collections.unmodifiableMap( linkedMap ); + } + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ItemDTO.java new file mode 100644 index 0000000000..c38e37f08a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3089/dto/ItemDTO.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.ap.test.bugs._3089.dto; + +import java.util.Map; + +/** + * @author Oliver Erhart + */ +public abstract class ItemDTO { + public abstract String getId(); + + public abstract Map getAttributes(); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java new file mode 100644 index 0000000000..e561f40bab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Mapper.java @@ -0,0 +1,77 @@ +/* + * 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.test.bugs._3104; + +import java.util.Collections; +import java.util.List; + +import org.mapstruct.BeanMapping; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE) +public interface Issue3104Mapper { + + Issue3104Mapper INSTANCE = Mappers.getMapper( Issue3104Mapper.class ); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) + void update(@MappingTarget Target target, Source source); + + class Target { + private List children = Collections.emptyList(); + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + if ( children == null ) { + throw new IllegalArgumentException( "children is null" ); + } + this.children = Collections.unmodifiableList( children ); + } + } + + class Child { + private String myField; + + public String getMyField() { + return myField; + } + + public void setMyField(String myField) { + this.myField = myField; + } + } + + class Source { + private final List children; + + public Source(List children) { + this.children = children; + } + + public List getChildren() { + return children; + } + + } + + class ChildSource { + private final String myField; + + public ChildSource(String myField) { + this.myField = myField; + } + + public String getMyField() { + return myField; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.java new file mode 100644 index 0000000000..5f3a9e160b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3104/Issue3104Test.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.test.bugs._3104; + +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3104") +@WithClasses(Issue3104Mapper.class) +class Issue3104Test { + + @ProcessorTest + void shouldCorrectlyMapUpdateMappingWithTargetImmutableCollectionStrategy() { + Issue3104Mapper.Target target = new Issue3104Mapper.Target(); + Issue3104Mapper.INSTANCE.update( target, new Issue3104Mapper.Source( null ) ); + + assertThat( target.getChildren() ).isEmpty(); + + Issue3104Mapper.INSTANCE.update( + target, + new Issue3104Mapper.Source( Collections.singletonList( new Issue3104Mapper.ChildSource( "tester" ) ) ) + ); + assertThat( target.getChildren() ) + .extracting( Issue3104Mapper.Child::getMyField ) + .containsExactly( "tester" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.java new file mode 100644 index 0000000000..12dd823760 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110Mapper.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.ap.test.bugs._3110; + +import org.mapstruct.EnumMapping; +import org.mapstruct.Mapper; + +@Mapper +public interface Issue3110Mapper { + enum SourceEnum { + FOO, BAR + } + + enum TargetEnum { + FOO, BAR + } + + class CustomCheckedException extends Exception { + public CustomCheckedException(String message) { + super( message ); + } + } + + @EnumMapping(unexpectedValueMappingException = CustomCheckedException.class) + TargetEnum map(SourceEnum sourceEnum) throws CustomCheckedException; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.java new file mode 100644 index 0000000000..3b256ba100 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3110/Issue3110MapperTest.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.ap.test.bugs._3110; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + Issue3110Mapper.class +}) +@IssueKey("3110") +class Issue3110MapperTest { + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void throwsException() { + generatedSource.forMapper( Issue3110Mapper.class ).content() + .contains( "throws CustomCheckedException" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java new file mode 100644 index 0000000000..e680db9317 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Mapper.java @@ -0,0 +1,92 @@ +/* + * 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.test.bugs._3126; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface Issue3126Mapper { + + Issue3126Mapper INSTANCE = Mappers.getMapper( Issue3126Mapper.class ); + + @SubclassMapping(target = HomeAddressDto.class, source = HomeAddress.class) + @SubclassMapping(target = OfficeAddressDto.class, source = OfficeAddress.class) + @Mapping(target = ".", source = "auditable") + AddressDto map(Address address); + + interface AddressDto { + + } + + class HomeAddressDto implements AddressDto { + private String createdBy; + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + } + + class OfficeAddressDto implements AddressDto { + private String createdBy; + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + } + + class HomeAddress extends Address { + + public HomeAddress(Auditable auditable) { + super( auditable ); + } + } + + class OfficeAddress extends Address { + + public OfficeAddress(Auditable auditable) { + super( auditable ); + } + } + + abstract class Address { + + private final Auditable auditable; + + protected Address(Auditable auditable) { + this.auditable = auditable; + } + + public Auditable getAuditable() { + return auditable; + } + } + + class Auditable { + + private final String createdBy; + + public Auditable(String createdBy) { + this.createdBy = createdBy; + } + + public String getCreatedBy() { + return createdBy; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.java new file mode 100644 index 0000000000..35e3eb873c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3126/Issue3126Test.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.ap.test.bugs._3126; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3126") +@WithClasses(Issue3126Mapper.class) +class Issue3126Test { + + @ProcessorTest + void shouldCompile() { + Issue3126Mapper.Auditable auditable = new Issue3126Mapper.Auditable( "home-user" ); + Issue3126Mapper.Address address = new Issue3126Mapper.HomeAddress( auditable ); + Issue3126Mapper.AddressDto addressDto = Issue3126Mapper.INSTANCE.map( address ); + + assertThat( addressDto ).isInstanceOfSatisfying( Issue3126Mapper.HomeAddressDto.class, homeAddress -> { + assertThat( homeAddress.getCreatedBy() ).isEqualTo( "home-user" ); + } ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java new file mode 100644 index 0000000000..54ed903ee7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Exception.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._3142; + +/** + * @author Filip Hrisafov + */ +public class Issue3142Exception extends Exception { + + public Issue3142Exception(String message) { + super( message ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java new file mode 100644 index 0000000000..a8b44874bb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142Test.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._3142; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Issue3142Exception.class, + Source.class, + Target.class, +}) +@IssueKey("3142") +class Issue3142Test { + + @ProcessorTest + @WithClasses({ + Issue3142WithBeforeMappingExceptionMapper.class + }) + void exceptionThrownInBeforeMapping() { + assertThatThrownBy( () -> Issue3142WithBeforeMappingExceptionMapper.INSTANCE.map( + new Source( new Source.Nested( "throwException" ) ), "test" ) + ) + .isInstanceOf( Issue3142Exception.class ) + .hasMessage( "Source nested exception" ); + } + + @ProcessorTest + @WithClasses({ + Issue3142WithAfterMappingExceptionMapper.class + }) + void exceptionThrownInAfterMapping() { + assertThatThrownBy( () -> Issue3142WithAfterMappingExceptionMapper.INSTANCE.map( + new Source( new Source.Nested( "throwException" ) ), "test" ) + ) + .isInstanceOf( Issue3142Exception.class ) + .hasMessage( "Source nested exception" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java new file mode 100644 index 0000000000..e188626424 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithAfterMappingExceptionMapper.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._3142; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3142WithAfterMappingExceptionMapper { + + Issue3142WithAfterMappingExceptionMapper INSTANCE = + Mappers.getMapper( Issue3142WithAfterMappingExceptionMapper.class ); + + Target map(Source source, String id) throws Issue3142Exception; + + @AfterMapping + default void afterMappingValidation(Object source) throws Issue3142Exception { + if ( source instanceof Source.Nested ) { + Source.Nested nested = (Source.Nested) source; + if ( "throwException".equals( nested.getValue() ) ) { + throw new Issue3142Exception( "Source nested exception" ); + } + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java new file mode 100644 index 0000000000..ee27d87b7f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Issue3142WithBeforeMappingExceptionMapper.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._3142; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3142WithBeforeMappingExceptionMapper { + + Issue3142WithBeforeMappingExceptionMapper INSTANCE = + Mappers.getMapper( Issue3142WithBeforeMappingExceptionMapper.class ); + + Target map(Source source, String id) throws Issue3142Exception; + + @BeforeMapping + default void preMappingValidation(Object source) throws Issue3142Exception { + if ( source instanceof Source.Nested ) { + Source.Nested nested = (Source.Nested) source; + if ( "throwException".equals( nested.getValue() ) ) { + throw new Issue3142Exception( "Source nested exception" ); + } + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.java new file mode 100644 index 0000000000..2b78ffa022 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Source.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.ap.test.bugs._3142; + +/** + * @author Filip Hrisafov + */ +public class Source { + private final Nested nested; + + public Source(Nested nested) { + this.nested = nested; + } + + public Nested getNested() { + return nested; + } + + public static class Nested { + private final String value; + + public Nested(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.java new file mode 100644 index 0000000000..6f832b6ecf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3142/Target.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.ap.test.bugs._3142; + +/** + * @author Filip Hrisafov + */ +public class Target { + private String id; + private Nested nested; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public static class Nested { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java new file mode 100644 index 0000000000..df5b767876 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.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.ap.test.bugs._3144; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface Issue3144Mapper { + + Issue3144Mapper INSTANCE = Mappers.getMapper( Issue3144Mapper.class ); + + @Mapping(target = "map", source = "sourceMap") + Target mapExplicitDefined(Map sourceMap); + + @Mapping(target = "map", ignore = true) + Target map(Map sourceMap); + + Target mapMultiParameters(Source source, Map map); + + @Mapping(target = "value", source = "map.testValue") + Target mapMultiParametersDefinedMapping(Source source, Map map); + + class Source { + private final String sourceValue; + + public Source(String sourceValue) { + this.sourceValue = sourceValue; + } + + public String getSourceValue() { + return sourceValue; + } + } + + class Target { + private String value; + private Map map; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java new file mode 100644 index 0000000000..13f349414a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java @@ -0,0 +1,63 @@ +/* + * 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.test.bugs._3144; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3144Mapper.class) +@IssueKey("3144") +class Issue3144Test { + + @ProcessorTest + void shouldCorrectlyHandleMapBeanMapping() { + Map map = new HashMap<>(); + map.put( "value", "Map Value" ); + map.put( "testValue", "Map Test Value" ); + + Issue3144Mapper.Target target = Issue3144Mapper.INSTANCE.mapExplicitDefined( map ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "Map Value" ); + assertThat( target.getMap() ) + .containsOnly( + entry( "value", "Map Value" ), + entry( "testValue", "Map Test Value" ) + ); + + target = Issue3144Mapper.INSTANCE.map( map ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "Map Value" ); + assertThat( target.getMap() ).isNull(); + + target = Issue3144Mapper.INSTANCE.mapMultiParameters( null, map ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + assertThat( target.getMap() ) + .containsOnly( + entry( "value", "Map Value" ), + entry( "testValue", "Map Test Value" ) + ); + + target = Issue3144Mapper.INSTANCE.mapMultiParametersDefinedMapping( null, map ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "Map Test Value" ); + assertThat( target.getMap() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.java new file mode 100644 index 0000000000..af54d23a8d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Mapper.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.ap.test.bugs._3153; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +interface Issue3153Mapper { + + Issue3153Mapper INSTANCE = Mappers.getMapper( Issue3153Mapper.class ); + + @ValueMapping(source = " PR", target = "PR") + @ValueMapping(source = " PR", target = "PR") + @ValueMapping(source = " PR", target = "PR") + @ValueMapping(source = MappingConstants.ANY_REMAINING, target = MappingConstants.NULL) + Target mapToEnum(String value); + + @ValueMapping(source = "PR", target = " PR") + String mapFromEnum(Target value); + + enum Target { + PR, + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.java new file mode 100644 index 0000000000..658a497086 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3153/Issue3153Test.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.test.bugs._3153; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3153Mapper.class) +@IssueKey("3153") +class Issue3153Test { + + @ProcessorTest + void shouldNotTrimStringValueSource() { + assertThat( Issue3153Mapper.INSTANCE.mapToEnum( "PR" ) ).isEqualTo( Issue3153Mapper.Target.PR ); + assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR ); + assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR ); + assertThat( Issue3153Mapper.INSTANCE.mapToEnum( " PR" ) ).isEqualTo( Issue3153Mapper.Target.PR ); + + assertThat( Issue3153Mapper.INSTANCE.mapFromEnum( Issue3153Mapper.Target.PR ) ).isEqualTo( " PR" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.java new file mode 100644 index 0000000000..25af6b3d44 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Mapper.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.ap.test.bugs._3158; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3158Mapper { + + Issue3158Mapper INSTANCE = Mappers.getMapper( Issue3158Mapper.class ); + + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "name") + Target map(Target target); + + class Target { + private final String name; + private final String email; + + public Target(String name, String email) { + this.name = name; + this.email = email; + } + + public String getName() { + return name; + } + + public String getEmail() { + return email; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.java new file mode 100644 index 0000000000..e4832edfa9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3158/Issue3158Test.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.ap.test.bugs._3158; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3158Mapper.class) +@IssueKey("3158") +class Issue3158Test { + + @ProcessorTest + void beanMappingIgnoreByDefaultShouldBeRespectedForConstructorProperties() { + Issue3158Mapper.Target target = Issue3158Mapper.INSTANCE.map( new Issue3158Mapper.Target( + "tester", + "tester@test.com" + ) ); + + assertThat( target.getName() ).isEqualTo( "tester" ); + assertThat( target.getEmail() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java new file mode 100644 index 0000000000..42c77c71dd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Mapper.java @@ -0,0 +1,64 @@ +/* + * 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.test.bugs._3159; + +import java.util.Collection; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3159Mapper { + + Issue3159Mapper INSTANCE = Mappers.getMapper( Issue3159Mapper.class ); + + @Mapping(target = "elements", defaultExpression = "java(new ArrayList<>())") + Target map(Source source); + + default String elementName(Element element) { + return element != null ? element.getName() : null; + } + + class Target { + private final Collection elements; + + public Target(Collection elements) { + this.elements = elements; + } + + public Collection getElements() { + return elements; + } + } + + class Source { + private final Collection elements; + + public Source(Collection elements) { + this.elements = elements; + } + + public Collection getElements() { + return elements; + } + } + + class Element { + private final String name; + + public Element(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.java new file mode 100644 index 0000000000..77feba151c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3159/Issue3159Test.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.ap.test.bugs._3159; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3159") +@WithClasses(Issue3159Mapper.class) +class Issue3159Test { + + @ProcessorTest + void shouldUseDefaultExpressionForCollection() { + Issue3159Mapper.Target target = Issue3159Mapper.INSTANCE.map( new Issue3159Mapper.Source( null ) ); + + assertThat( target.getElements() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Mapper.java new file mode 100644 index 0000000000..bd44a9d386 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Mapper.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.ap.test.bugs._3163; + +import java.util.Optional; + +import org.mapstruct.Mapper; + +@Mapper +public interface Issue3163Mapper { + + Target map(Source value); + + Target.Nested map(Source.Nested value); + + default Optional wrapAsOptional(T value) { + return Optional.ofNullable( value ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Test.java new file mode 100644 index 0000000000..5c16a707f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Issue3163Test.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.ap.test.bugs._3163; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@WithClasses({ + Issue3163Mapper.class, + Source.class, + Target.class +}) +class Issue3163Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Source.java new file mode 100644 index 0000000000..8f5a57f1bf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Source.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.test.bugs._3163; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final Nested nested; + + public Source(Nested nested) { + this.nested = nested; + } + + public Nested getNested() { + return nested; + } + + public static class Nested { + private final String value; + + public Nested(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Target.java new file mode 100644 index 0000000000..301a281d2d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3163/Target.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.test.bugs._3163; + +import java.util.Optional; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private Nested nested; + + public Optional getNested() { + return Optional.ofNullable( nested ); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + public final void setNested(Optional nested) { + this.nested = nested.orElse( null ); + } + + public static class Nested { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java new file mode 100644 index 0000000000..b6d5fc1ed7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165Mapper.java @@ -0,0 +1,65 @@ +/* + * 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.test.bugs._3165; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue3165Mapper { + + Issue3165Mapper INSTANCE = Mappers.getMapper( Issue3165Mapper.class ); + + Target toTarget(Source source); + + class Source { + private String[] pets; + private Iterable cats; + + public Source(String[] pets, Iterable cats) { + this.pets = pets; + this.cats = cats; + } + + public String[] getPets() { + return pets; + } + + public Iterable getCats() { + return cats; + } + } + + class Target { + private List pets; + private List cats; + + Target() { + this.pets = new ArrayList<>(); + this.cats = new ArrayList<>(); + } + + public List getPets() { + return pets; + } + + public void addPet(String pet) { + pets.add( pet ); + } + + public List getCats() { + return cats; + } + + public void addCat(String cat) { + cats.add( cat ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java new file mode 100644 index 0000000000..4fb9e0ab3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3165/Issue3165MapperTest.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._3165; + +import java.util.Arrays; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Issue3165Mapper.class +}) +@IssueKey("3165") +class Issue3165MapperTest { + + @ProcessorTest + void supportsAdderWhenMappingArrayAndIterableToCollection() { + Issue3165Mapper.Source src = new Issue3165Mapper.Source( + new String[] { "cat", "dog", "mouse" }, + Arrays.asList( "ivy", "flu", "freya" ) + ); + Issue3165Mapper.Target target = Issue3165Mapper.INSTANCE.toTarget( src ); + assertThat( target.getPets() ).containsExactly( "cat", "dog", "mouse" ); + assertThat( target.getCats() ).containsExactly( "ivy", "flu", "freya" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.java new file mode 100644 index 0000000000..fd0d4672a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/ErroneousIssue3238Mapper.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.ap.test.bugs._3238; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface ErroneousIssue3238Mapper { + + @Mapping(target = ".", ignore = true) + Target map(Source source); + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.java new file mode 100644 index 0000000000..6648495a70 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3238/Issue3238Test.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.test.bugs._3238; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@WithClasses(ErroneousIssue3238Mapper.class) +@IssueKey("3238") +class Issue3238Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = @Diagnostic( type = ErroneousIssue3238Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "Using @Mapping( target = \".\", ignore = true ) is not allowed." + + " You need to use @BeanMapping( ignoreByDefault = true ) if you would like to ignore" + + " all non explicitly mapped target properties." + ) + ) + void shouldGenerateValidCompileError() { + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.java new file mode 100644 index 0000000000..e0cbba40f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Mapper.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.ap.test.bugs._3248; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface Issue3248Mapper { + + @BeanMapping(ignoreUnmappedSourceProperties = "otherValue") + Target map(Source source); + + @InheritConfiguration + Target secondMap(Source source); + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public String getOtherValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.java new file mode 100644 index 0000000000..ad8cf2e499 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3248/Issue3248Test.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.ap.test.bugs._3248; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3248") +@WithClasses({ + Issue3248Mapper.class +}) +class Issue3248Test { + + @ProcessorTest + void shouldCompileCode() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Entity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Entity.java new file mode 100644 index 0000000000..cfd038b03c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Entity.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.ap.test.bugs._3296; + +public class Entity { + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Issue3296Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Issue3296Test.java new file mode 100644 index 0000000000..9bd28d333a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Issue3296Test.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.ap.test.bugs._3296; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Ben Zegveld + */ +@IssueKey( "3296" ) +@WithClasses( { Entity.class, Payload.class } ) +public class Issue3296Test { + + @ProcessorTest + @WithClasses( { MapperExtendingConfig.class, MapperConfigWithPayloadArgument.class } ) + public void shouldNotRaiseErrorForDefaultAfterMappingMethodImplementation() { + Payload payload = new Payload(); + payload.setName( "original" ); + + Entity entity = Mappers.getMapper( MapperExtendingConfig.class ).toEntity( payload ); + + assertThat( entity.getName() ).isEqualTo( "AfterMapping called" ); + } + + @ProcessorTest + @WithClasses( { MapperNotExtendingConfig.class, MapperConfigWithoutPayloadArgument.class } ) + public void shouldNotRaiseErrorRequiringArgumentsForDefaultMethods() { + Payload payload = new Payload(); + payload.setName( "original" ); + + Entity entity = Mappers.getMapper( MapperNotExtendingConfig.class ).toEntity( payload ); + + assertThat( entity.getName() ).isEqualTo( "original" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithPayloadArgument.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithPayloadArgument.java new file mode 100644 index 0000000000..69826aa10e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithPayloadArgument.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.ap.test.bugs._3296; + +import org.mapstruct.AfterMapping; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingTarget; + +@MapperConfig +public interface MapperConfigWithPayloadArgument { + + @AfterMapping + default void afterMapping(@MappingTarget Entity entity, Payload unused) { + staticMethod( entity ); + } + + static void staticMethod(Entity entity) { + entity.setName( "AfterMapping called" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithoutPayloadArgument.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithoutPayloadArgument.java new file mode 100644 index 0000000000..36cc046d12 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperConfigWithoutPayloadArgument.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.ap.test.bugs._3296; + +import org.mapstruct.AfterMapping; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingTarget; + +@MapperConfig +public interface MapperConfigWithoutPayloadArgument { + + @AfterMapping + default void afterMapping(@MappingTarget Entity entity) { + staticMethod( entity ); + } + + static void staticMethod(Entity entity) { + entity.setName( "AfterMapping called" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperExtendingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperExtendingConfig.java new file mode 100644 index 0000000000..157bd051cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperExtendingConfig.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.ap.test.bugs._3296; + +import org.mapstruct.Mapper; + +@Mapper( config = MapperConfigWithPayloadArgument.class ) +public interface MapperExtendingConfig extends MapperConfigWithPayloadArgument { + Entity toEntity(Payload payload); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperNotExtendingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperNotExtendingConfig.java new file mode 100644 index 0000000000..2ceb18c253 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/MapperNotExtendingConfig.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.ap.test.bugs._3296; + +import org.mapstruct.Mapper; + +@Mapper( config = MapperConfigWithoutPayloadArgument.class ) +public interface MapperNotExtendingConfig { + Entity toEntity(Payload payload); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Payload.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Payload.java new file mode 100644 index 0000000000..2502e5e4a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3296/Payload.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.ap.test.bugs._3296; + +public class Payload { + + String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Mapper.java new file mode 100644 index 0000000000..2975a5de8a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Mapper.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.ap.test.bugs._3310; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface Issue3310Mapper { + + Issue3310Mapper INSTANCE = Mappers.getMapper( Issue3310Mapper.class ); + + Target map(Source source); + + abstract class BaseClass { + + private List items; + + public List getItems() { + return items; + } + + public void setItems(List items) { + throw new UnsupportedOperationException( "adder should be used instead" ); + } + + public void addItem(T item) { + if ( items == null ) { + items = new ArrayList<>(); + } + items.add( item ); + } + } + + class Target extends BaseClass { + + } + + class Source { + + private final List items; + + public Source(List items) { + this.items = items; + } + + public List getItems() { + return items; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Test.java new file mode 100644 index 0000000000..a00526985f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3310/Issue3310Test.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.ap.test.bugs._3310; + +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3310") +@WithClasses(Issue3310Mapper.class) +class Issue3310Test { + + @ProcessorTest + void shouldUseAdderWithGenericBaseClass() { + Issue3310Mapper.Source source = new Issue3310Mapper.Source( Collections.singletonList( "test" ) ); + Issue3310Mapper.Target target = Issue3310Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getItems() ).containsExactly( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Mapper.java new file mode 100644 index 0000000000..33a40b1dfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Mapper.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.ap.test.bugs._3317; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3317Mapper { + + Issue3317Mapper INSTANCE = Mappers.getMapper( Issue3317Mapper.class ); + + Target map(int id, long value); + + class Target { + + private final int id; + private final long value; + + public Target(int id, long value) { + this.id = id; + this.value = value; + } + + public int getId() { + return id; + } + + public long getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Test.java new file mode 100644 index 0000000000..218a1f8979 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3317/Issue3317Test.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.ap.test.bugs._3317; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3317") +@WithClasses(Issue3317Mapper.class) +class Issue3317Test { + + @ProcessorTest + void shouldGenerateValidCode() { + Issue3317Mapper.Target target = Issue3317Mapper.INSTANCE.map( 10, 42L ); + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( 10 ); + assertThat( target.getValue() ).isEqualTo( 42L ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.java new file mode 100644 index 0000000000..3011479c65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Mapper.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.ap.test.bugs._3331; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface Issue3331Mapper { + + Issue3331Mapper INSTANCE = Mappers.getMapper( Issue3331Mapper.class ); + + @SubclassMapping(source = Vehicle.Car.class, target = VehicleDto.Car.class) + @SubclassMapping(source = Vehicle.Motorbike.class, target = VehicleDto.Motorbike.class) + @Mapping(target = "name", constant = "noname") + VehicleDto map(Vehicle vehicle); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java new file mode 100644 index 0000000000..3871868226 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Issue3331Test.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._3331; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3331") +@WithClasses({ + Issue3331Mapper.class, + Vehicle.class, + VehicleDto.class, +}) +class Issue3331Test { + + @ProcessorTest + void shouldCorrectCompileAndThrowExceptionOnRuntime() { + VehicleDto target = Issue3331Mapper.INSTANCE.map( new Vehicle.Car( "Test car", 4 ) ); + + assertThat( target.getName() ).isEqualTo( "noname" ); + assertThat( target ) + .isInstanceOfSatisfying( VehicleDto.Car.class, car -> { + assertThat( car.getNumOfDoors() ).isEqualTo( 4 ); + } ); + + target = Issue3331Mapper.INSTANCE.map( new Vehicle.Motorbike( "Test bike", true ) ); + + assertThat( target.getName() ).isEqualTo( "noname" ); + assertThat( target ) + .isInstanceOfSatisfying( VehicleDto.Motorbike.class, bike -> { + assertThat( bike.isAllowedForMinor() ).isTrue(); + } ); + + assertThatThrownBy( () -> Issue3331Mapper.INSTANCE.map( new Vehicle.Truck( "Test truck", 3 ) ) ) + .isInstanceOf( IllegalArgumentException.class ) + .hasMessage( "Not all subclasses are supported for this mapping. Missing for " + Vehicle.Truck.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java new file mode 100644 index 0000000000..3b68a3acc4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/Vehicle.java @@ -0,0 +1,64 @@ +/* + * 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.test.bugs._3331; + +/** + * @author Filip Hrisafov + */ +public abstract class Vehicle { + + private final String name; + + protected Vehicle(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static class Car extends Vehicle { + + private final int numOfDoors; + + public Car(String name, int numOfDoors) { + super( name ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends Vehicle { + + private final boolean allowedForMinor; + + public Motorbike(String name, boolean allowedForMinor) { + super( name ); + this.allowedForMinor = allowedForMinor; + } + + public boolean isAllowedForMinor() { + return allowedForMinor; + } + } + + public static class Truck extends Vehicle { + + private final int numOfAxis; + + public Truck(String name, int numOfAxis) { + super( name ); + this.numOfAxis = numOfAxis; + } + + public int getNumOfAxis() { + return numOfAxis; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java new file mode 100644 index 0000000000..8c2ef13dd2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3331/VehicleDto.java @@ -0,0 +1,51 @@ +/* + * 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.test.bugs._3331; + +/** + * @author Filip Hrisafov + */ +public abstract class VehicleDto { + + private final String name; + + protected VehicleDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static class Car extends VehicleDto { + + private final int numOfDoors; + + public Car(String name, int numOfDoors) { + super( name ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends VehicleDto { + + private final boolean allowedForMinor; + + public Motorbike(String name, boolean allowedForMinor) { + super( name ); + this.allowedForMinor = allowedForMinor; + } + + public boolean isAllowedForMinor() { + return allowedForMinor; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.java new file mode 100644 index 0000000000..812d885489 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Mapper.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.test.bugs._3360; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper( + subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION, + unmappedTargetPolicy = ReportingPolicy.ERROR, + unmappedSourcePolicy = ReportingPolicy.ERROR +) +public interface Issue3360Mapper { + + Issue3360Mapper INSTANCE = Mappers.getMapper( Issue3360Mapper.class ); + + @SubclassMapping(target = VehicleDto.Car.class, source = Vehicle.Car.class) + VehicleDto map(Vehicle vehicle); + + @Mapping(target = "model", source = "modelName") + @BeanMapping(ignoreUnmappedSourceProperties = "computedName") + VehicleDto.Car map(Vehicle.Car car); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.java new file mode 100644 index 0000000000..307c2b265a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Issue3360Test.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.ap.test.bugs._3360; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3360") +@WithClasses({ + Issue3360Mapper.class, + Vehicle.class, + VehicleDto.class, +}) +class Issue3360Test { + + @ProcessorTest + void shouldCompileWithoutErrorsAndWarnings() { + + Vehicle vehicle = new Vehicle.Car( "Test", "car", 4 ); + + VehicleDto target = Issue3360Mapper.INSTANCE.map( vehicle ); + + assertThat( target.getName() ).isEqualTo( "Test" ); + assertThat( target.getModel() ).isEqualTo( "car" ); + assertThat( target ).isInstanceOfSatisfying( VehicleDto.Car.class, car -> { + assertThat( car.getNumOfDoors() ).isEqualTo( 4 ); + } ); + + assertThatThrownBy( () -> Issue3360Mapper.INSTANCE.map( new Vehicle.Motorbike( "Test", "bike" ) ) ) + .isInstanceOf( IllegalArgumentException.class ) + .hasMessage( "Not all subclasses are supported for this mapping. Missing for " + Vehicle.Motorbike.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.java new file mode 100644 index 0000000000..0cc2011bc9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/Vehicle.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.test.bugs._3360; + +/** + * @author Filip Hrisafov + */ +public abstract class Vehicle { + + private final String name; + private final String modelName; + + protected Vehicle(String name, String modelName) { + this.name = name; + this.modelName = modelName; + } + + public String getName() { + return name; + } + + public String getModelName() { + return modelName; + } + + public String getComputedName() { + return null; + } + + public static class Car extends Vehicle { + + private final int numOfDoors; + + public Car(String name, String modelName, int numOfDoors) { + super( name, modelName ); + this.numOfDoors = numOfDoors; + } + + public int getNumOfDoors() { + return numOfDoors; + } + } + + public static class Motorbike extends Vehicle { + + public Motorbike(String name, String modelName) { + super( name, modelName ); + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.java new file mode 100644 index 0000000000..fc93a2fcae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3360/VehicleDto.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.ap.test.bugs._3360; + +/** + * @author Filip Hrisafov + */ +public abstract class VehicleDto { + + private String name; + private String model; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public static class Car extends VehicleDto { + + private int numOfDoors; + + public int getNumOfDoors() { + return numOfDoors; + } + + public void setNumOfDoors(int numOfDoors) { + this.numOfDoors = numOfDoors; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Mapper.java new file mode 100644 index 0000000000..a556b63306 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Mapper.java @@ -0,0 +1,80 @@ +/* + * 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.test.bugs._3361; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class Issue3361Mapper { + + public static final Issue3361Mapper INSTANCE = Mappers.getMapper( Issue3361Mapper.class ); + + @Mapping(target = "someAttribute", source = "source.attribute") + @Mapping(target = "otherAttribute", source = "otherSource.anotherAttribute") + public abstract Target mapFromSource(Source source, OtherSource otherSource); + + @InheritConfiguration(name = "mapFromSource") + @Mapping(target = "otherAttribute", source = "source", qualifiedByName = "otherMapping") + public abstract Target mapInherited(Source source, OtherSource otherSource); + + @Named("otherMapping") + protected Long otherMapping(Source source) { + return source.getAttribute() != null ? 1L : 0L; + } + + public static class Target { + private String someAttribute; + private Long otherAttribute; + + public String getSomeAttribute() { + return someAttribute; + } + + public Target setSomeAttribute(String someAttribute) { + this.someAttribute = someAttribute; + return this; + } + + public Long getOtherAttribute() { + return otherAttribute; + } + + public Target setOtherAttribute(Long otherAttribute) { + this.otherAttribute = otherAttribute; + return this; + } + } + + public static class Source { + private final String attribute; + + public Source(String attribute) { + this.attribute = attribute; + } + + public String getAttribute() { + return attribute; + } + } + + public static class OtherSource { + + private final Long anotherAttribute; + + public OtherSource(Long anotherAttribute) { + this.anotherAttribute = anotherAttribute; + } + + public Long getAnotherAttribute() { + return anotherAttribute; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Test.java new file mode 100644 index 0000000000..3fa16ec1ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3361/Issue3361Test.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.test.bugs._3361; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3361") +@WithClasses(Issue3361Mapper.class) +class Issue3361Test { + + @ProcessorTest + void multiSourceShouldInherit() { + Issue3361Mapper.Source source = new Issue3361Mapper.Source( "Test" ); + Issue3361Mapper.OtherSource otherSource = new Issue3361Mapper.OtherSource( 10L ); + + Issue3361Mapper.Target target = Issue3361Mapper.INSTANCE.mapFromSource( source, otherSource ); + assertThat( target.getSomeAttribute() ).isEqualTo( "Test" ); + assertThat( target.getOtherAttribute() ).isEqualTo( 10L ); + + target = Issue3361Mapper.INSTANCE.mapInherited( source, otherSource ); + assertThat( target.getSomeAttribute() ).isEqualTo( "Test" ); + assertThat( target.getOtherAttribute() ).isEqualTo( 1L ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.java new file mode 100644 index 0000000000..55a3f783c3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370BuilderProvider.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.ap.test.bugs._3370; + +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.mapstruct.ap.spi.BuilderInfo; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesBuilderProvider; + +public class Issue3370BuilderProvider extends ImmutablesBuilderProvider implements BuilderProvider { + + @Override + protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement) { + Name name = typeElement.getQualifiedName(); + if ( name.toString().endsWith( ".Item" ) ) { + return super.findBuilderInfo( asImmutableElement( typeElement ) ); + } + return super.findBuilderInfo( typeElement ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.java new file mode 100644 index 0000000000..0389de5b46 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/Issue3370Test.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.test.bugs._3370; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.spi.BuilderProvider; +import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; +import org.mapstruct.ap.test.bugs._3370.domain.ImmutableItem; +import org.mapstruct.ap.test.bugs._3370.domain.Item; +import org.mapstruct.ap.test.bugs._3370.dto.ItemDTO; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithServiceImplementations; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ItemMapper.class, + Item.class, + ImmutableItem.class, + ItemDTO.class, +}) +@IssueKey("3370") +@WithServiceImplementations({ + @WithServiceImplementation(provides = BuilderProvider.class, value = Issue3370BuilderProvider.class), + @WithServiceImplementation(provides = AccessorNamingStrategy.class, + value = ImmutablesAccessorNamingStrategy.class), +}) +public class Issue3370Test { + + @ProcessorTest + public void shouldUseBuilderOfImmutableSuperClass() { + + Map attributesMap = new HashMap<>(); + attributesMap.put( "a", "b" ); + attributesMap.put( "c", "d" ); + + ItemDTO item = new ItemDTO( "test", attributesMap ); + + Item target = ItemMapper.INSTANCE.map( item ); + + assertThat( target ).isNotNull(); + assertThat( target.getId() ).isEqualTo( "test" ); + assertThat( target.getAttributes() ).isEqualTo( attributesMap ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.java new file mode 100644 index 0000000000..5583e191b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/ItemMapper.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.ap.test.bugs._3370; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._3370.domain.Item; +import org.mapstruct.ap.test.bugs._3370.dto.ItemDTO; +import org.mapstruct.factory.Mappers; + +@Mapper +public abstract class ItemMapper { + + public static final ItemMapper INSTANCE = Mappers.getMapper( ItemMapper.class ); + + public abstract Item map(ItemDTO itemDTO); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java new file mode 100644 index 0000000000..fabae2f0d0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/ImmutableItem.java @@ -0,0 +1,316 @@ +/* + * 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.test.bugs._3370.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * Immutable implementation of {@link Item}. + *

      + * Use the builder to create immutable instances: + * {@code new Item.Builder()}. + */ +@SuppressWarnings("all") +public final class ImmutableItem extends Item { + private final String id; + private final Map attributes; + + private ImmutableItem(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + /** + * @return The value of the {@code id} attribute + */ + @Override + public String getId() { + return id; + } + + /** + * @return The value of the {@code attributes} attribute + */ + @Override + public Map getAttributes() { + return attributes; + } + + /** + * Copy the current immutable object by setting a value for the {@link Item#getId() id} attribute. + * An equals check used to prevent copying of the same value by returning {@code this}. + * + * @param id A new value for id + * @return A modified copy of the {@code this} object + */ + public final ImmutableItem withId(String id) { + if ( this.id.equals( id ) ) { + return this; + } + String newValue = Objects.requireNonNull( id, "id" ); + return new ImmutableItem( newValue, this.attributes ); + } + + /** + * Copy the current immutable object by replacing the {@link Item#getAttributes() attributes} map with the specified map. + * Nulls are not permitted as keys or values. + * A shallow reference equality check is used to prevent copying of the same value by returning {@code this}. + * + * @param attributes The entries to be added to the attributes map + * @return A modified copy of {@code this} object + */ + public final ImmutableItem withAttributes(Map attributes) { + if ( this.attributes == attributes ) { + return this; + } + Map newValue = createUnmodifiableMap( true, false, attributes ); + return new ImmutableItem( this.id, newValue ); + } + + /** + * This instance is equal to all instances of {@code ImmutableItem} that have equal attribute values. + * + * @return {@code true} if {@code this} is equal to {@code another} instance + */ + @Override + public boolean equals(Object another) { + if ( this == another ) { + return true; + } + return another instanceof ImmutableItem + && equalTo( (ImmutableItem) another ); + } + + private boolean equalTo(ImmutableItem another) { + return id.equals( another.id ) + && attributes.equals( another.attributes ); + } + + /** + * Computes a hash code from attributes: {@code id}, {@code attributes}. + * + * @return hashCode value + */ + @Override + public int hashCode() { + int h = 31; + h = h * 17 + id.hashCode(); + h = h * 17 + attributes.hashCode(); + return h; + } + + /** + * Prints the immutable value {@code Item} with attribute values. + * + * @return A string representation of the value + */ + @Override + public String toString() { + return "Item{" + + "id=" + id + + ", attributes=" + attributes + + "}"; + } + + /** + * Creates an immutable copy of a {@link Item} value. + * Uses accessors to get values to initialize the new immutable instance. + * If an instance is already immutable, it is returned as is. + * + * @param instance The instance to copy + * @return A copied immutable Item instance + */ + public static ImmutableItem copyOf(Item instance) { + if ( instance instanceof ImmutableItem ) { + return (ImmutableItem) instance; + } + return new Item.Builder() + .from( instance ) + .build(); + } + + /** + * Builds instances of type {@link ImmutableItem ImmutableItem}. + * Initialize attributes and then invoke the {@link #build()} method to create an + * immutable instance. + *

      {@code Builder} is not thread-safe and generally should not be stored in a field or collection, + * but instead used immediately to create instances. + */ + public static class Builder { + private static final long INIT_BIT_ID = 0x1L; + private long initBits = 0x1L; + + private String id; + private Map attributes = new LinkedHashMap(); + + /** + * Creates a builder for {@link ImmutableItem ImmutableItem} instances. + */ + public Builder() { + if ( !( this instanceof Item.Builder ) ) { + throw new UnsupportedOperationException( "Use: new Item.Builder()" ); + } + } + + /** + * Fill a builder with attribute values from the provided {@code Item} instance. + * Regular attribute values will be replaced with those from the given instance. + * Absent optional values will not replace present values. + * Collection elements and entries will be added, not replaced. + * + * @param instance The instance from which to copy values + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder from(Item instance) { + Objects.requireNonNull( instance, "instance" ); + id( instance.getId() ); + putAllAttributes( instance.getAttributes() ); + return (Item.Builder) this; + } + + /** + * Initializes the value for the {@link Item#getId() id} attribute. + * + * @param id The value for id + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder id(String id) { + this.id = Objects.requireNonNull( id, "id" ); + initBits &= ~INIT_BIT_ID; + return (Item.Builder) this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. + * + * @param key The key in the attributes map + * @param value The associated value in the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAttributes(String key, String value) { + this.attributes.put( + Objects.requireNonNull( key, "attributes key" ), + Objects.requireNonNull( value, "attributes value" ) + ); + return (Item.Builder) this; + } + + /** + * Put one entry to the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param entry The key and value entry + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAttributes(Map.Entry entry) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + return (Item.Builder) this; + } + + /** + * Sets or replaces all mappings from the specified map as entries for the {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param attributes The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder attributes(Map attributes) { + this.attributes.clear(); + return putAllAttributes( attributes ); + } + + /** + * Put all mappings from the specified map as entries to {@link Item#getAttributes() attributes} map. Nulls are not permitted + * + * @param attributes The entries that will be added to the attributes map + * @return {@code this} builder for use in a chained invocation + */ + public final Item.Builder putAllAttributes(Map attributes) { + for ( Map.Entry entry : attributes.entrySet() ) { + String k = entry.getKey(); + String v = entry.getValue(); + this.attributes.put( + Objects.requireNonNull( k, "attributes key" ), + Objects.requireNonNull( v, "attributes value" ) + ); + } + return (Item.Builder) this; + } + + /** + * Builds a new {@link ImmutableItem ImmutableItem}. + * + * @return An immutable instance of Item + * @throws java.lang.IllegalStateException if any required attributes are missing + */ + public ImmutableItem build() { + if ( initBits != 0 ) { + throw new IllegalStateException( formatRequiredAttributesMessage() ); + } + return new ImmutableItem( id, createUnmodifiableMap( false, false, attributes ) ); + } + + private String formatRequiredAttributesMessage() { + List attributes = new ArrayList<>(); + if ( ( initBits & INIT_BIT_ID ) != 0 ) { + attributes.add( "id" ); + } + return "Cannot build Item, some of required attributes are not set " + attributes; + } + } + + private static Map createUnmodifiableMap(boolean checkNulls, boolean skipNulls, + Map map) { + switch ( map.size() ) { + case 0: + return Collections.emptyMap(); + case 1: { + Map.Entry e = map.entrySet().iterator().next(); + K k = e.getKey(); + V v = e.getValue(); + if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + if ( skipNulls && ( k == null || v == null ) ) { + return Collections.emptyMap(); + } + return Collections.singletonMap( k, v ); + } + default: { + Map linkedMap = new LinkedHashMap( map.size() ); + if ( skipNulls || checkNulls ) { + for ( Map.Entry e : map.entrySet() ) { + K k = e.getKey(); + V v = e.getValue(); + if ( skipNulls ) { + if ( k == null || v == null ) { + continue; + } + } + else if ( checkNulls ) { + Objects.requireNonNull( k, "key" ); + Objects.requireNonNull( v, "value" ); + } + linkedMap.put( k, v ); + } + } + else { + linkedMap.putAll( map ); + } + return Collections.unmodifiableMap( linkedMap ); + } + } + } +} \ No newline at end of file diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.java new file mode 100644 index 0000000000..a78c3bc007 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/domain/Item.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.ap.test.bugs._3370.domain; + +import java.util.Collections; +import java.util.Map; + +public abstract class Item { + + public abstract String getId(); + + public abstract Map getAttributes(); + + public static Item.Builder builder() { + return new Item.Builder(); + } + + public static class Builder extends ImmutableItem.Builder { + + public ImmutableItem.Builder addSomeData(String key, String data) { + return super.attributes( Collections.singletonMap( key, data ) ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.java new file mode 100644 index 0000000000..70dba87fe4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3370/dto/ItemDTO.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.ap.test.bugs._3370.dto; + +import java.util.Map; + +public class ItemDTO { + private final String id; + private final Map attributes; + + public ItemDTO(String id, Map attributes) { + this.id = id; + this.attributes = attributes; + } + + public String getId() { + return id; + } + + public Map getAttributes() { + return attributes; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.java new file mode 100644 index 0000000000..784e47b8e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Erroneous3413Mapper.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.ap.test.bugs._3413; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Muhammad Usama + */ +@Mapper +public interface Erroneous3413Mapper { + Erroneous3413Mapper INSTANCE = Mappers.getMapper( Erroneous3413Mapper.class ); + + @Mapping(target = "", expression = "", conditionQualifiedByName = "") + ToPOJO map(FromPOJO fromPOJO); + + class FromPOJO { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class ToPOJO { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.java new file mode 100644 index 0000000000..f98ed9cdbd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3413/Issue3413Test.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.test.bugs._3413; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Muhammad Usama + */ +@IssueKey("3413") +public class Issue3413Test { + @ProcessorTest + @WithClasses(Erroneous3413Mapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Expression and condition qualified by name are both defined in @Mapping, " + + "either define an expression or a condition qualified by name." + ) + } + ) + void errorExpectedBecauseExpressionAndConditionQualifiedByNameCannotCoExists() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java new file mode 100644 index 0000000000..1bf87672c6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Mapper.java @@ -0,0 +1,56 @@ +/* + * 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.test.bugs._3462; + +import java.util.List; +import java.util.stream.Stream; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3462Mapper { + + Issue3462Mapper INSTANCE = Mappers.getMapper( Issue3462Mapper.class ); + + Target map(Source source); + + class Source { + private final List values; + + public Source(List values) { + this.values = values; + } + + public List getValues() { + return values; + } + + public Stream getValuesStream() { + return values != null ? values.stream() : Stream.empty(); + } + } + + class Target { + private List values; + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + public Stream getValuesStream() { + return values != null ? values.stream() : Stream.empty(); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.java new file mode 100644 index 0000000000..16be4f441e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3462/Issue3462Test.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.test.bugs._3462; + +import java.util.Arrays; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3462") +@WithClasses(Issue3462Mapper.class) +class Issue3462Test { + + @ProcessorTest + void shouldNotTreatStreamGettersAsAlternativeSetter() { + + Issue3462Mapper.Source source = new Issue3462Mapper.Source( Arrays.asList( "first", "second" ) ); + Issue3462Mapper.Target target = Issue3462Mapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getValues() ).containsExactly( "first", "second" ); + assertThat( target.getValuesStream() ).containsExactly( "first", "second" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java new file mode 100644 index 0000000000..6b0b71993e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/EntityBuilder.java @@ -0,0 +1,14 @@ +/* + * 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.test.bugs._3463; + +/** + * @author Filip Hrisafov + */ +public interface EntityBuilder { + + T build(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.java new file mode 100644 index 0000000000..37ead3dfdf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Mapper.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.ap.test.bugs._3463; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3463Mapper { + + Issue3463Mapper INSTANCE = Mappers.getMapper( Issue3463Mapper.class ); + + Person map(PersonDto dto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.java new file mode 100644 index 0000000000..e6cfc3ece7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Issue3463Test.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.ap.test.bugs._3463; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3463") +@WithClasses({ + EntityBuilder.class, + Issue3463Mapper.class, + Person.class, + PersonDto.class +}) +class Issue3463Test { + + @ProcessorTest + void shouldUseInterfaceBuildMethod() { + Person person = Issue3463Mapper.INSTANCE.map( new PersonDto( "Tester" ) ); + + assertThat( person ).isNotNull(); + assertThat( person.getName() ).isEqualTo( "Tester" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.java new file mode 100644 index 0000000000..977d619474 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/Person.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.ap.test.bugs._3463; + +/** + * @author Filip Hrisafov + */ +public class Person { + + private final String name; + + private Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder builder() { + return new BuilderImpl(); + } + + public interface Builder extends EntityBuilder { + + Builder name(String name); + } + + private static final class BuilderImpl implements Builder { + + private String name; + + private BuilderImpl() { + } + + @Override + public Builder name(String name) { + this.name = name; + return this; + } + + @Override + public Person build() { + return new Person( this.name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.java new file mode 100644 index 0000000000..447e4813cc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3463/PersonDto.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.ap.test.bugs._3463; + +/** + * @author Filip Hrisafov + */ +public class PersonDto { + private final String name; + + public PersonDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.java new file mode 100644 index 0000000000..dc1558907c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/ErroneousIssue3485Mapper.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.test.bugs._3485; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author hduelme + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface ErroneousIssue3485Mapper { + + ErroneousIssue3485Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3485Mapper.class ); + + class Target { + private final String value; + + public Target( String value ) { + this.value = value; + } + + public String getValue() { + return value; + } + + } + + @Mapping(target = ".") + Target targetFromExpression(String s); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.java new file mode 100644 index 0000000000..964de36ec8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3485/Issue3485Test.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.ap.test.bugs._3485; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author hduelme + */ +@IssueKey("3485") +public class Issue3485Test { + + @ProcessorTest + @WithClasses(ErroneousIssue3485Mapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousIssue3485Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 34, + message = "Using @Mapping( target = \".\") requires a source property. Expression or " + + "constant cannot be used as a source.") + }) + void thisMappingWithoutSource() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.java new file mode 100644 index 0000000000..fd1137137b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Mapper.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.ap.test.bugs._3561; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3561Mapper { + + Issue3561Mapper INSTANCE = Mappers.getMapper( Issue3561Mapper.class ); + + @Mapping(target = "value", conditionQualifiedByName = "shouldMapValue") + Target map(Source source, @Context boolean shouldMapValue); + + @Condition + @Named("shouldMapValue") + default boolean shouldMapValue(@Context boolean shouldMapValue) { + return shouldMapValue; + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Source { + + private String value; + private boolean valueInitialized; + + public String getValue() { + if ( valueInitialized ) { + return value; + } + + throw new IllegalStateException( "value is not initialized" ); + } + + public void setValue(String value) { + this.valueInitialized = true; + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.java new file mode 100644 index 0000000000..7cd0c26694 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3561/Issue3561Test.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.test.bugs._3561; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3561Mapper.class) +@IssueKey("3561") +class Issue3561Test { + + @ProcessorTest + void shouldCorrectlyUseConditionWithContext() { + + Issue3561Mapper.Source source = new Issue3561Mapper.Source(); + + assertThatThrownBy( () -> Issue3561Mapper.INSTANCE.map( source, true ) ) + .isInstanceOf( IllegalStateException.class ) + .hasMessage( "value is not initialized" ); + + Issue3561Mapper.Target target = Issue3561Mapper.INSTANCE.map( source, false ); + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java new file mode 100644 index 0000000000..b86e667e56 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Mapper.java @@ -0,0 +1,57 @@ +/* + * 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.test.bugs._3565; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3565Mapper { + + Issue3565Mapper INSTANCE = Mappers.getMapper( Issue3565Mapper.class ); + + default T mapFromOptional(Optional value) { + return value.orElse( (T) null ); + } + + @Condition + default boolean isOptionalPresent(Optional value) { + return value.isPresent(); + } + + Target map(Source source); + + class Source { + + private final Boolean condition; + + public Source(Boolean condition) { + this.condition = condition; + } + + public Optional getCondition() { + return Optional.ofNullable( this.condition ); + } + } + + class Target { + private String condition; + + public Optional getCondition() { + return Optional.ofNullable( this.condition ); + } + + public void setCondition(String condition) { + this.condition = condition; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.java new file mode 100644 index 0000000000..c4cd028d45 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3565/Issue3565Test.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.ap.test.bugs._3565; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3565Mapper.class) +@IssueKey("3565") +class Issue3565Test { + + @ProcessorTest + void shouldGenerateValidCode() { + Issue3565Mapper.Target target = Issue3565Mapper.INSTANCE.map( new Issue3565Mapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getCondition() ).isEmpty(); + + target = Issue3565Mapper.INSTANCE.map( new Issue3565Mapper.Source( false ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getCondition() ).hasValue( "false" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.java new file mode 100644 index 0000000000..7da423c906 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Bean.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.test.bugs._3591; + +import java.util.List; + +public class Bean { + private List beans; + private String value; + + public Bean() { + } + + public Bean(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getBeans() { + return beans; + } + + public void setBeans(List beans) { + this.beans = beans; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.java new file mode 100644 index 0000000000..00e70ff228 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanDto.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.test.bugs._3591; + +import java.util.List; + +public class BeanDto { + + private List beans; + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public List getBeans() { + return beans; + } + + public void setBeans(List beans) { + this.beans = beans; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.java new file mode 100644 index 0000000000..eeb54b4710 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/BeanMapper.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.ap.test.bugs._3591; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BeanMapper { + + BeanMapper INSTANCE = Mappers.getMapper( BeanMapper.class ); + + @Mapping(source = "beans", target = "beans") + BeanDto map(Bean bean, @MappingTarget BeanDto beanDto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java new file mode 100644 index 0000000000..b0e68ecfcf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBean.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._3591; + +import java.util.Map; +import java.util.stream.Stream; + +public class ContainerBean { + + private String value; + private Map beanMap; + private Stream beanStream; + + public ContainerBean() { + } + + public ContainerBean(String value) { + this.value = value; + } + + public Map getBeanMap() { + return beanMap; + } + + public void setBeanMap(Map beanMap) { + this.beanMap = beanMap; + } + + public Stream getBeanStream() { + return beanStream; + } + + public void setBeanStream(Stream beanStream) { + this.beanStream = beanStream; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java new file mode 100644 index 0000000000..86bb0193ac --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanDto.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._3591; + +import java.util.Map; +import java.util.stream.Stream; + +public class ContainerBeanDto { + + private String value; + private Map beanMap; + private Stream beanStream; + + public Map getBeanMap() { + return beanMap; + } + + public void setBeanMap(Map beanMap) { + this.beanMap = beanMap; + } + + public Stream getBeanStream() { + return beanStream; + } + + public void setBeanStream(Stream beanStream) { + this.beanStream = beanStream; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.java new file mode 100644 index 0000000000..0da338aadf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/ContainerBeanMapper.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.ap.test.bugs._3591; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ContainerBeanMapper { + + ContainerBeanMapper INSTANCE = Mappers.getMapper( ContainerBeanMapper.class ); + + @Mapping(source = "beanMap", target = "beanMap") + @Mapping(source = "beanStream", target = "beanStream") + ContainerBeanDto mapWithMapMapping(ContainerBean containerBean, @MappingTarget ContainerBeanDto containerBeanDto); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java new file mode 100644 index 0000000000..15bb191ffc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3591/Issue3591Test.java @@ -0,0 +1,79 @@ +/* + * 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.test.bugs._3591; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3591") +class Issue3591Test { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + BeanDto.class, + Bean.class, + BeanMapper.class + }) + void mapNestedBeansWithMappingAnnotation() { + Bean bean = new Bean( "parent" ); + Bean child = new Bean( "child" ); + bean.setBeans( Collections.singletonList( child ) ); + + BeanDto beanDto = BeanMapper.INSTANCE.map( bean, new BeanDto() ); + + assertThat( beanDto ).isNotNull(); + assertThat( beanDto.getValue() ).isEqualTo( "parent" ); + assertThat( beanDto.getBeans() ) + .extracting( BeanDto::getValue ) + .containsExactly( "child" ); + } + + @ProcessorTest + @WithClasses({ + ContainerBean.class, + ContainerBeanDto.class, + ContainerBeanMapper.class, + }) + void shouldMapNestedMapAndStream() { + generatedSource.addComparisonToFixtureFor( ContainerBeanMapper.class ); + + ContainerBean containerBean = new ContainerBean( "parent" ); + Map beanMap = new HashMap<>(); + beanMap.put( "child", new ContainerBean( "mapChild" ) ); + containerBean.setBeanMap( beanMap ); + + Stream streamChild = Stream.of( new ContainerBean( "streamChild" ) ); + containerBean.setBeanStream( streamChild ); + + ContainerBeanDto dto = ContainerBeanMapper.INSTANCE.mapWithMapMapping( containerBean, new ContainerBeanDto() ); + + assertThat( dto ).isNotNull(); + + assertThat( dto.getBeanMap() ) + .extractingByKey( "child" ) + .extracting( ContainerBeanDto::getValue ) + .isEqualTo( "mapChild" ); + + assertThat( dto.getBeanStream() ) + .singleElement() + .extracting( ContainerBeanDto::getValue ) + .isEqualTo( "streamChild" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.java new file mode 100644 index 0000000000..851e353de7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Mapper.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.ap.test.bugs._3601; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourceParameterCondition; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3601Mapper { + + Issue3601Mapper INSTANCE = Mappers.getMapper( Issue3601Mapper.class ); + + @Mapping(target = "currentId", source = "source.uuid") + @Mapping(target = "targetIds", source = "sourceIds") + Target map(Source source, List sourceIds); + + @SourceParameterCondition + default boolean isNotEmpty(List elements) { + return elements != null && !elements.isEmpty(); + } + + class Source { + private final String uuid; + + public Source(String uuid) { + this.uuid = uuid; + } + + public String getUuid() { + return uuid; + } + } + + class Target { + //CHECKSTYLE:OFF + public String currentId; + public List targetIds; + //CHECKSTYLE:ON + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java new file mode 100644 index 0000000000..b9b68fb583 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3601/Issue3601Test.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._3601; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3601") +class Issue3601Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses( Issue3601Mapper.class ) + void shouldUseSourceParameterPresenceCheckCorrectly() { + Issue3601Mapper.Target target = Issue3601Mapper.INSTANCE.map( + new Issue3601Mapper.Source( "test1" ), + Collections.emptyList() + ); + + assertThat( target ).isNotNull(); + assertThat( target.currentId ).isEqualTo( "test1" ); + assertThat( target.targetIds ).isNull(); + + target = Issue3601Mapper.INSTANCE.map( + null, + Collections.emptyList() + ); + + assertThat( target ).isNull(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Mapper.java new file mode 100644 index 0000000000..41b1e2aefa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Mapper.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.ap.test.bugs._3609; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.SubclassMapping; + +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public abstract class Issue3609Mapper { + + @SubclassMapping(source = CarDto.class, target = Car.class) + @BeanMapping(ignoreUnmappedSourceProperties = "id") + public abstract Vehicle toVehicle(VehicleDto vehicle); + + //CHECKSTYLE:OFF + public static class Vehicle { + public int price; + } + + public static class Car extends Vehicle { + public int seats; + + public Car(int price, int seats) { + this.price = price; + this.seats = seats; + } + } + + public static class VehicleDto { + public int id; + public int price; + } + + public static class CarDto extends VehicleDto { + public int seats; + } + //CHECKSTYLE:ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Test.java new file mode 100644 index 0000000000..8a57a1357f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3609/Issue3609Test.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.ap.test.bugs._3609; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Roman Obolonskyii + */ +@IssueKey("3609") +@WithClasses(Issue3609Mapper.class) +public class Issue3609Test { + + @ProcessorTest + void shouldCompileWithoutErrors() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.java new file mode 100644 index 0000000000..3ac8b595ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Bar.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.test.bugs._3652; + +public class Bar { + + private int secret; + private int doesNotExistInFoo; + + public int getSecret() { + return secret; + } + + public void setSecret(int secret) { + this.secret = secret; + } + + public int getDoesNotExistInFoo() { + return doesNotExistInFoo; + } + + public void setDoesNotExistInFoo(int doesNotExistInFoo) { + this.doesNotExistInFoo = doesNotExistInFoo; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.java new file mode 100644 index 0000000000..02b5b6e8b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Foo.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.ap.test.bugs._3652; + +public class Foo { + + private int secret; + + public int getSecret() { + return secret; + } + + public void setSecret(int secret) { + this.secret = secret; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.java new file mode 100644 index 0000000000..3cf19dfbfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarConfig.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.ap.test.bugs._3652; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.MappingInheritanceStrategy; + +@MapperConfig(mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG) +public interface FooBarConfig { + + @Mapping(target = "doesNotExistInFoo", ignore = true) + @Mapping(target = "secret", ignore = true) + Bar toBar(Foo foo); + + @InheritInverseConfiguration(name = "toBar") + Foo toFoo(Bar bar); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.java new file mode 100644 index 0000000000..d58be74f1e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/FooBarMapper.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.ap.test.bugs._3652; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(config = FooBarConfig.class) +public interface FooBarMapper { + + FooBarMapper INSTANCE = Mappers.getMapper( FooBarMapper.class ); + + Bar toBar(Foo foo); + + Foo toFoo(Bar bar); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.java new file mode 100644 index 0000000000..aa8a64ada8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3652/Issue3652Test.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.test.bugs._3652; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3652") +public class Issue3652Test { + + @WithClasses({ + Bar.class, + Foo.class, + FooBarConfig.class, + FooBarMapper.class, + }) + @ProcessorTest + void ignoreMappingsWithoutSourceShouldBeInvertible() { + Bar bar = new Bar(); + bar.setSecret( 123 ); + bar.setDoesNotExistInFoo( 6 ); + + Foo foo = FooBarMapper.INSTANCE.toFoo( bar ); + + assertThat( foo.getSecret() ).isEqualTo( 0 ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.java new file mode 100644 index 0000000000..d4f2dff0af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Mapper.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.ap.test.bugs._3667; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3667Mapper { + + Issue3667Mapper INSTANCE = Mappers.getMapper( Issue3667Mapper.class ); + + @Mapping(target = "nested.value", source = "nested.nested1.value") + Target mapFirst(Source source); + + @Mapping(target = "nested.value", source = "nested.nested2.value") + Target mapSecond(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.java new file mode 100644 index 0000000000..1e91de00d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Issue3667Test.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.ap.test.bugs._3667; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3667") +@WithClasses({ + Issue3667Mapper.class, + Source.class, + Target.class +}) +class Issue3667Test { + + @ProcessorTest + void shouldCorrectlyMapNestedProperty() { + Source source = new Source( + new Source.Nested( + new Source.NestedNested( "value1" ), + new Source.NestedNested( "value2" ) + ) + ); + + Target target1 = Issue3667Mapper.INSTANCE.mapFirst( source ); + Target target2 = Issue3667Mapper.INSTANCE.mapSecond( source ); + + assertThat( target1 ).isNotNull(); + assertThat( target1.getNested() ).isNotNull(); + assertThat( target1.getNested().getValue() ).isEqualTo( "value1" ); + + assertThat( target2 ).isNotNull(); + assertThat( target2.getNested() ).isNotNull(); + assertThat( target2.getNested().getValue() ).isEqualTo( "value2" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java new file mode 100644 index 0000000000..ede78edf0d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Source.java @@ -0,0 +1,51 @@ +/* + * 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.test.bugs._3667; + +public class Source { + + private final Nested nested; + + public Source(Nested nested) { + this.nested = nested; + } + + public Nested getNested() { + return nested; + } + + public static class Nested { + + private final NestedNested nested1; + private final NestedNested nested2; + + public Nested(NestedNested nested1, NestedNested nested2) { + this.nested1 = nested1; + this.nested2 = nested2; + } + + public NestedNested getNested1() { + return nested1; + } + + public NestedNested getNested2() { + return nested2; + } + } + + public static class NestedNested { + + private final String value; + + public NestedNested(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java new file mode 100644 index 0000000000..e0c8d23296 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3667/Target.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._3667; + +public class Target { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public static class Nested { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.java new file mode 100644 index 0000000000..3c10c4d470 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Child.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.ap.test.bugs._3668; + +public abstract class Child { + + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public static class ChildA extends Child { } + + public static class ChildB extends Child { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.java new file mode 100644 index 0000000000..458cc57a9a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildDto.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.ap.test.bugs._3668; + +public abstract class ChildDto { + + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public static class ChildDtoA extends ChildDto { } + + public static class ChildDtoB extends ChildDto { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.java new file mode 100644 index 0000000000..c381a99fcd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ChildMapper.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.ap.test.bugs._3668; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; + +@Mapper(subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface ChildMapper { + + @SubclassMapping(target = Child.ChildA.class, source = ChildDto.ChildDtoA.class) + @SubclassMapping(target = Child.ChildB.class, source = ChildDto.ChildDtoB.class) + Child toEntity(ChildDto childDto); + + @SubclassMapping(target = ChildDto.ChildDtoA.class, source = Child.ChildA.class) + @SubclassMapping(target = ChildDto.ChildDtoB.class, source = Child.ChildB.class) + ChildDto toDto(Child child); + + Child.ChildA toEntity(ChildDto.ChildDtoA childDto); + + ChildDto.ChildDtoA toDto(Child.ChildA child); + + Child.ChildB toEntity(ChildDto.ChildDtoB childDto); + + ChildDto.ChildDtoB toDto(Child.ChildB child); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.java new file mode 100644 index 0000000000..a35ac70dce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Issue3668Test.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.test.bugs._3668; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3668") +@WithClasses({ + Child.class, + ChildDto.class, + ChildMapper.class, + Parent.class, + ParentDto.class, + ParentMapper.class, +}) +class Issue3668Test { + + @ProcessorTest + void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.java new file mode 100644 index 0000000000..900a7fa68b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/Parent.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.ap.test.bugs._3668; + +public abstract class Parent { + + private Long id; + + private T child; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public T getChild() { + return child; + } + + public void setChild(T child) { + this.child = child; + } + + public static class ParentA extends Parent { } + + public static class ParentB extends Parent { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.java new file mode 100644 index 0000000000..f4736ceef0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentDto.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.ap.test.bugs._3668; + +public abstract class ParentDto { + + private Long id; + + private T child; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public T getChild() { + return child; + } + + public void setChild(T child) { + this.child = child; + } + + public static class ParentDtoA extends ParentDto { } + + public static class ParentDtoB extends ParentDto { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.java new file mode 100644 index 0000000000..484ddbc930 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3668/ParentMapper.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.ap.test.bugs._3668; + +import org.mapstruct.Mapper; +import org.mapstruct.SubclassExhaustiveStrategy; +import org.mapstruct.SubclassMapping; + +@Mapper(uses = { ChildMapper.class }, subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION) +public interface ParentMapper { + + @SubclassMapping(target = Parent.ParentA.class, source = ParentDto.ParentDtoA.class) + @SubclassMapping(target = Parent.ParentB.class, source = ParentDto.ParentDtoB.class) + Parent toEntity(ParentDto parentDto); + + @SubclassMapping(target = ParentDto.ParentDtoA.class, source = Parent.ParentA.class) + @SubclassMapping(target = ParentDto.ParentDtoB.class, source = Parent.ParentB.class) + ParentDto toDto(Parent parent); + + Parent.ParentA toEntity(ParentDto.ParentDtoA parentDto); + + ParentDto.ParentDtoA toDto(Parent.ParentA parent); + + Parent.ParentB toEntity(ParentDto.ParentDtoB parentDto); + + ParentDto.ParentDtoB toDto(Parent.ParentB parent); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.java new file mode 100644 index 0000000000..a014a7d52d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Mapper.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.ap.test.bugs._3670; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3670Mapper { + + @Mapping(target = "name", source = ".", qualifiedByName = "nestedName") + Target map(Source source); + + @InheritInverseConfiguration + @Mapping(target = "nested.nestedName", source = "name") + Source map(Target target); + + @Named("nestedName") + default String mapNestedName(Source source) { + if ( source == null ) { + return null; + } + + Nested nested = source.getNested(); + + return nested != null ? nested.getNestedName() : null; + } + + class Target { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class Nested { + private String nestedName; + + public String getNestedName() { + return nestedName; + } + + public void setNestedName(String nestedName) { + this.nestedName = nestedName; + } + } + + class Source { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.java new file mode 100644 index 0000000000..fc3929b685 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3670/Issue3670Test.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.ap.test.bugs._3670; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3670") +@WithClasses(Issue3670Mapper.class) +class Issue3670Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.java new file mode 100644 index 0000000000..9fe97e0e79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Animal.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.ap.test.bugs._3673; + +public class Animal { + + private AnimalDetails details; + + public AnimalDetails getDetails() { + return details; + } + + public void setDetails(AnimalDetails details) { + this.details = details; + } + + public enum Type { + CAT, + DOG + } + + public static class AnimalDetails { + private Type type; + private String name; + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.java new file mode 100644 index 0000000000..63e5391690 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Cat.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.ap.test.bugs._3673; + +public class Cat { + + private final Details details; + + public Cat(Details details) { + this.details = details; + } + + public Details getDetails() { + return details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.java new file mode 100644 index 0000000000..8526793015 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Details.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.ap.test.bugs._3673; + +public class Details { + + private final String name; + + public Details(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.java new file mode 100644 index 0000000000..a021a5d579 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Dog.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.ap.test.bugs._3673; + +public class Dog { + + private final Details details; + + public Dog(Details details) { + this.details = details; + } + + public Details getDetails() { + return details; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.java new file mode 100644 index 0000000000..d74a954f9f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ConstantMapper.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.ap.test.bugs._3673; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3673ConstantMapper { + + Issue3673ConstantMapper INSTANCE = Mappers.getMapper( Issue3673ConstantMapper.class ); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", constant = "DOG") + Animal map(Dog dog); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", constant = "CAT") + Animal map(Cat cat); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.java new file mode 100644 index 0000000000..3aca966807 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673ExpressionMapper.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.ap.test.bugs._3673; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3673ExpressionMapper { + + Issue3673ExpressionMapper INSTANCE = Mappers.getMapper( Issue3673ExpressionMapper.class ); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", expression = "java(Animal.Type.DOG)") + Animal map(Dog dog); + + @Mapping(target = "details.name", source = "details.name") + @Mapping(target = "details.type", expression = "java(Animal.Type.CAT)") + Animal map(Cat cat); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java new file mode 100644 index 0000000000..2d796b4670 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3673/Issue3673Test.java @@ -0,0 +1,68 @@ +/* + * 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.test.bugs._3673; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3673") +@WithClasses({ + Cat.class, + Dog.class, + Details.class, + Animal.class +}) +class Issue3673Test { + + @ProcessorTest + @WithClasses(Issue3673ConstantMapper.class) + void shouldCorrectlyMapNestedPropertyConstant() { + + Animal cat = Issue3673ConstantMapper.INSTANCE.map( + new Cat( new Details( "cat" ) ) + ); + + Animal dog = Issue3673ConstantMapper.INSTANCE.map( + new Dog( new Details( "dog" ) ) + ); + + assertThat( cat ).isNotNull(); + assertThat( cat.getDetails() ).isNotNull(); + assertThat( cat.getDetails().getName() ).isEqualTo( "cat" ); + assertThat( cat.getDetails().getType() ).isEqualTo( Animal.Type.CAT ); + + assertThat( dog ).isNotNull(); + assertThat( dog.getDetails() ).isNotNull(); + assertThat( dog.getDetails().getName() ).isEqualTo( "dog" ); + assertThat( dog.getDetails().getType() ).isEqualTo( Animal.Type.DOG ); + } + + @ProcessorTest + @WithClasses(Issue3673ExpressionMapper.class) + void shouldCorrectlyMapNestedPropertyExpression() { + + Animal cat = Issue3673ExpressionMapper.INSTANCE.map( + new Cat( new Details( "cat" ) ) + ); + + Animal dog = Issue3673ExpressionMapper.INSTANCE.map( + new Dog( new Details( "dog" ) ) + ); + + assertThat( cat ).isNotNull(); + assertThat( cat.getDetails() ).isNotNull(); + assertThat( cat.getDetails().getName() ).isEqualTo( "cat" ); + assertThat( cat.getDetails().getType() ).isEqualTo( Animal.Type.CAT ); + + assertThat( dog ).isNotNull(); + assertThat( dog.getDetails() ).isNotNull(); + assertThat( dog.getDetails().getName() ).isEqualTo( "dog" ); + assertThat( dog.getDetails().getType() ).isEqualTo( Animal.Type.DOG ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java new file mode 100644 index 0000000000..374fbf931a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Mapper.java @@ -0,0 +1,128 @@ +/* + * 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.test.bugs._3678; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3678Mapper { + + Issue3678Mapper INSTANCE = Mappers.getMapper( Issue3678Mapper.class ); + + @Mapping(target = "name", source = "sourceA.name") + @Mapping(target = "description", source = "sourceB.description") + Target map(SourceA sourceA, SourceB sourceB, @Context MappingContext context); + + @Mapping(target = "description", constant = "some description") + Target map(SourceA sourceA, @Context MappingContext context); + + class MappingContext { + + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + public void beforeMappingSourceA(SourceA sourceA) { + invokedMethods.add( "beforeMappingSourceA" ); + } + + @AfterMapping + public void afterMappingSourceB(SourceA sourceA) { + invokedMethods.add( "afterMappingSourceA" ); + } + + @BeforeMapping + public void beforeMappingSourceB(SourceB sourceB) { + invokedMethods.add( "beforeMappingSourceB" ); + } + + @AfterMapping + public void afterMappingSourceB(SourceB sourceB) { + invokedMethods.add( "afterMappingSourceB" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } + } + + class SourceA { + + private final String name; + + public SourceA(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class SourceB { + + private final String description; + + public SourceB(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + } + + final class Target { + + private final String name; + private final String description; + + private Target(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private String name; + private String description; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder description(String description) { + this.description = description; + return this; + } + + public Target build() { + return new Target( this.name, this.description ); + } + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.java new file mode 100644 index 0000000000..25e7e21622 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3678/Issue3678Test.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.ap.test.bugs._3678; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3678") +@WithClasses(Issue3678Mapper.class) +public class Issue3678Test { + + @ProcessorTest + void beforeAndAfterMappingOnlyCalledOnceForTwoSources() { + + Issue3678Mapper.MappingContext mappingContext = new Issue3678Mapper.MappingContext(); + Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" ); + Issue3678Mapper.SourceB sourceB = new Issue3678Mapper.SourceB( "description" ); + Issue3678Mapper.INSTANCE.map( sourceA, sourceB, mappingContext ); + + assertThat( mappingContext.getInvokedMethods() ) + .containsExactly( + "beforeMappingSourceA", + "beforeMappingSourceB", + "afterMappingSourceA", + "afterMappingSourceB" + ); + } + + @ProcessorTest + void beforeAndAfterMappingOnlyCalledOnceForSingleSource() { + + Issue3678Mapper.MappingContext mappingContext = new Issue3678Mapper.MappingContext(); + Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" ); + Issue3678Mapper.INSTANCE.map( sourceA, mappingContext ); + + assertThat( mappingContext.getInvokedMethods() ) + .containsExactly( + "beforeMappingSourceA", + "afterMappingSourceA" + ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.java new file mode 100644 index 0000000000..287e7655fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Mapper.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.test.bugs._3703; + +import org.mapstruct.AfterMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ap.test.bugs._3703.dto.Contact; + +@Mapper +public interface Issue3703Mapper { + + Contact map(org.mapstruct.ap.test.bugs._3703.entity.Contact contact); + + org.mapstruct.ap.test.bugs._3703.entity.Contact map(Contact contact); + + @AfterMapping + default void afterMapping(@MappingTarget Contact target, org.mapstruct.ap.test.bugs._3703.entity.Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget Contact.Builder targetBuilder, + org.mapstruct.ap.test.bugs._3703.entity.Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget org.mapstruct.ap.test.bugs._3703.entity.Contact target, Contact contact) { + } + + @AfterMapping + default void afterMapping(@MappingTarget org.mapstruct.ap.test.bugs._3703.entity.Contact.Builder targetBuilder, + Contact contact) { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.java new file mode 100644 index 0000000000..ac5cae3959 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/Issue3703Test.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.ap.test.bugs._3703; + +import org.mapstruct.ap.test.bugs._3703.dto.Contact; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("3703") +@WithClasses({ + Contact.class, + org.mapstruct.ap.test.bugs._3703.entity.Contact.class, + Issue3703Mapper.class +}) +public class Issue3703Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.java new file mode 100644 index 0000000000..a949864791 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/dto/Contact.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.test.bugs._3703.dto; + +public class Contact { + + private final String name; + + private Contact(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Contact build() { + return new Contact( name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.java new file mode 100644 index 0000000000..31fde373fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3703/entity/Contact.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.test.bugs._3703.entity; + +public class Contact { + + private final String name; + + private Contact(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Contact build() { + return new Contact( name ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.java new file mode 100644 index 0000000000..e2a04fe336 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/BaseMapper.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.ap.test.bugs._3711; + +import org.mapstruct.Context; + +interface BaseMapper { + E toEntity(T s, @Context JpaContext ctx); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.java new file mode 100644 index 0000000000..7b141e9e14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/Issue3711Test.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.ap.test.bugs._3711; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ParentEntity.class, + ParentDto.class, + JpaContext.class, + SourceTargetMapper.class, + BaseMapper.class, +}) +@IssueKey("3711") +class Issue3711Test { + @ProcessorTest + void shouldGenerateContextMethod() { + JpaContext jpaContext = new JpaContext<>(); + SourceTargetMapper.INSTANCE.toEntity( new ParentDto(), jpaContext ); + + assertThat( jpaContext.getInvokedMethods() ) + .containsExactly( "beforeMapping", "afterMapping" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.java new file mode 100644 index 0000000000..b21bf639d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/JpaContext.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.ap.test.bugs._3711; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; + +public class JpaContext { + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + void beforeMapping(@MappingTarget T parentEntity) { + invokedMethods.add( "beforeMapping" ); + } + + @AfterMapping + void afterMapping(@MappingTarget T parentEntity) { + invokedMethods.add( "afterMapping" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.java new file mode 100644 index 0000000000..664d6e58ab --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentDto.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.ap.test.bugs._3711; + +public class ParentDto { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.java new file mode 100644 index 0000000000..aaefa949fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/ParentEntity.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.ap.test.bugs._3711; + +public class ParentEntity { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java new file mode 100644 index 0000000000..b09333fd56 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3711/SourceTargetMapper.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._3711; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper extends BaseMapper { + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + ParentEntity toDTO(ParentDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Branch.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Branch.java index f6c513d77c..2f3165b32f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Branch.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Branch.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._373; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/BranchLocation.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/BranchLocation.java index 3c2439d7e8..b5d83e015e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/BranchLocation.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/BranchLocation.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._373; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Country.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Country.java index e8ca505d9f..9d6b827626 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Country.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Country.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._373; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Mapper.java index a626d7bfde..ac64cd7db1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Mapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._373; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Test.java index e4a59713b9..91c01ac7c2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Test.java @@ -1,28 +1,13 @@ -/** - * Copyright 2012-2015 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.bugs._373; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/373. @@ -30,10 +15,9 @@ * @author Sjaak Derksen */ @IssueKey( "373" ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue373Test { - @Test + @ProcessorTest @WithClasses( { Issue373Mapper.class, Branch.class, BranchLocation.class, Country.class, ResultDto.class } ) public void shouldForgeCorrectEntityBranchLocationCountry() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/ResultDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/ResultDto.java index fb2f97981b..d3799efbed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/ResultDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/ResultDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._373; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java new file mode 100644 index 0000000000..79b13aa8ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Mapper.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._3732; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3732Mapper { + + Target map(Source source); + + Source map(Target source); + + class Source { + private LocalDateTime value; + + public LocalDateTime getValue() { + return value; + } + + public void setValue(LocalDateTime value) { + this.value = value; + } + } + + class Target { + + private LocalDate value; + + public LocalDate getValue() { + return value; + } + + public void setValue(LocalDate value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.java new file mode 100644 index 0000000000..9dbd9c0bb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3732/Issue3732Test.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.ap.test.bugs._3732; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3732") +@WithClasses({ Issue3732Mapper.class }) +class Issue3732Test { + + @ProcessorTest + void shouldGenerateCorrectMapper() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Mapper.java index 6069f2297a..b12bbd5e07 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Mapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.bugs._374; import java.util.List; import java.util.Map; + import org.mapstruct.BeanMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -46,6 +34,4 @@ public interface Issue374Mapper { List mapIterable(List source, @MappingTarget List target); Map mapMap(Map source, @MappingTarget Map target); - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java index 8366e5ed0a..2bc6d6a170 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._374; @@ -22,12 +9,12 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; + import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; + +import static org.assertj.core.api.Assertions.assertThat; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/306. @@ -35,10 +22,9 @@ * @author Sjaak Derksen */ @IssueKey( "306" ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue374Test { - @Test + @ProcessorTest @WithClasses( { Issue374Mapper.class, Source.class, Target.class } ) public void shouldMapExistingTargetToDefault() { @@ -49,40 +35,41 @@ public void shouldMapExistingTargetToDefault() { assertThat( result.getConstant() ).isEqualTo( "test" ); } - @Test + @ProcessorTest @WithClasses( { Issue374Mapper.class, Source.class, Target.class } ) public void shouldMapExistingTargetWithConstantToDefault() { Target target2 = new Target(); Target result2 = Issue374Mapper.INSTANCE.map2( null, target2 ); - assertThat( result2 ).isNull(); + assertThat( result2 ).isNotNull(); + assertThat( result2 ).isEqualTo( target2 ); assertThat( target2.getTest() ).isNull(); assertThat( target2.getConstant() ).isNull(); } - @Test + @ProcessorTest @WithClasses( { Issue374Mapper.class, Source.class, Target.class } ) public void shouldMapExistingIterableTargetToDefault() { - List targetList = new ArrayList(); + List targetList = new ArrayList<>(); targetList.add( "test" ); List resultList = Issue374Mapper.INSTANCE.mapIterable( null, targetList ); assertThat( resultList ).isEqualTo( targetList ); assertThat( targetList ).isEmpty(); } - @Test + @ProcessorTest @WithClasses( { Issue374Mapper.class, Source.class, Target.class } ) public void shouldMapExistingMapTargetToDefault() { - Map targetMap = new HashMap(); + Map targetMap = new HashMap<>(); targetMap.put( 5, "test" ); Map resultMap = Issue374Mapper.INSTANCE.mapMap( null, targetMap ); assertThat( resultMap ).isEmpty(); assertThat( resultMap ).isEqualTo( resultMap ); } - @Test + @ProcessorTest @WithClasses( { Issue374VoidMapper.class, Source.class, Target.class } ) public void shouldMapExistingTargetVoidReturnToDefault() { @@ -92,21 +79,21 @@ public void shouldMapExistingTargetVoidReturnToDefault() { assertThat( target.getConstant() ).isEqualTo( "test" ); } - @Test + @ProcessorTest @WithClasses( { Issue374VoidMapper.class, Source.class, Target.class } ) public void shouldMapExistingIterableTargetVoidReturnToDefault() { - List targetList = new ArrayList(); + List targetList = new ArrayList<>(); targetList.add( "test" ); Issue374VoidMapper.INSTANCE.mapIterable( null, targetList ); assertThat( targetList ).isEmpty(); } - @Test + @ProcessorTest @WithClasses( { Issue374VoidMapper.class, Source.class, Target.class } ) public void shouldMapExistingMapTargetVoidReturnToDefault() { - Map targetMap = new HashMap(); + Map targetMap = new HashMap<>(); targetMap.put( 5, "test" ); Issue374VoidMapper.INSTANCE.mapMap( null, targetMap ); assertThat( targetMap ).isEmpty(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374VoidMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374VoidMapper.java index 60ca29d9e0..6fa27a806c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374VoidMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374VoidMapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.bugs._374; import java.util.List; import java.util.Map; + import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; @@ -41,6 +29,4 @@ public interface Issue374VoidMapper { void mapIterable(List source, @MappingTarget List target); void mapMap(Map source, @MappingTarget Map target); - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Source.java index 81dc3af0b5..349fd4a1e7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._374; @@ -33,7 +20,4 @@ public String getTest() { public void setTest(String test) { this.test = test; } - - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Target.java index 29dcd95fdf..ab87548921 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._374; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.java new file mode 100644 index 0000000000..04addc43f3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Mapper.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.ap.test.bugs._3747; + +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) +public interface Issue3747Mapper { + + Target map(Source source); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.java new file mode 100644 index 0000000000..57650e4dc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3747/Issue3747Test.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.ap.test.bugs._3747; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3747") +@WithClasses(Issue3747Mapper.class) +class Issue3747Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void shouldNotGenerateObsoleteCode() { + generatedSource.addComparisonToFixtureFor( Issue3747Mapper.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Case.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Case.java index 77c186abaf..2eb9e1aaa5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Case.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Case.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._375; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Int.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Int.java index 27654ff4b9..0006257cfa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Int.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Int.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._375; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Mapper.java index 7e4c754594..acc76e04c9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Mapper.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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._375; - import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Test.java index ecccce07bf..3cbd2fcad1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Test.java @@ -1,28 +1,13 @@ -/** - * Copyright 2012-2015 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.bugs._375; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/375. @@ -30,10 +15,9 @@ * @author Sjaak Derksen */ @IssueKey( "375" ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue375Test { - @Test + @ProcessorTest @WithClasses( { Issue375Mapper.class, Source.class, Target.class, Int.class, Case.class } ) public void shouldForgeNewMappings() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Source.java index 84d868aecb..2418fb7fdf 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Source.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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.test.bugs._375; - /** * * @author Sjaak Derksen diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Target.java index d021672121..80a86f2cb2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._375; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/ErroneousByteArrayMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/ErroneousByteArrayMapper.java new file mode 100644 index 0000000000..abf84f8228 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/ErroneousByteArrayMapper.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.ap.test.bugs._3786; + +import org.mapstruct.Mapper; + +@Mapper +public interface ErroneousByteArrayMapper { + byte[] map( String something ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/Issue3786Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/Issue3786Test.java new file mode 100644 index 0000000000..bd71392c30 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3786/Issue3786Test.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.test.bugs._3786; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Ben Zegveld + */ +@IssueKey( "3786" ) +public class Issue3786Test { + + @WithClasses( ErroneousByteArrayMapper.class ) + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = ErroneousByteArrayMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 12, + message = "Can't generate mapping method from non-iterable type to array." + ) + } + ) + void byteArrayReturnTypeShouldGiveInaccessibleContructorError() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java new file mode 100644 index 0000000000..89f325dfbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Mapper.java @@ -0,0 +1,63 @@ +/* + * 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.test.bugs._3806; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface Issue3806Mapper { + + Issue3806Mapper INSTANCE = Mappers.getMapper( Issue3806Mapper.class ); + + void update(@MappingTarget Target target, Target source); + + class Target { + + private final Collection authors; + private final Map booksByAuthor; + + protected Collection books; + protected Map booksByPublisher; + + public Target(Collection authors, Map booksByAuthor) { + this.authors = authors != null ? new ArrayList<>( authors ) : null; + this.booksByAuthor = booksByAuthor != null ? new HashMap<>( booksByAuthor ) : null; + } + + public Collection getAuthors() { + return authors; + } + + public Map getBooksByAuthor() { + return booksByAuthor; + } + + public Collection getBooks() { + return books; + } + + public void setBooks(Collection books) { + this.books = books; + } + + public Map getBooksByPublisher() { + return booksByPublisher; + } + + public void setBooksByPublisher(Map booksByPublisher) { + this.booksByPublisher = booksByPublisher; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java new file mode 100644 index 0000000000..1df3318c22 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3806/Issue3806Test.java @@ -0,0 +1,86 @@ +/* + * 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.test.bugs._3806; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses(Issue3806Mapper.class) +@IssueKey("3806") +class Issue3806Test { + + @ProcessorTest + void shouldNotClearGetterOnlyCollectionsInUpdateMapping() { + Map booksByAuthor = new HashMap<>(); + booksByAuthor.put( "author1", "book1" ); + booksByAuthor.put( "author2", "book2" ); + List authors = new ArrayList<>(); + authors.add( "author1" ); + authors.add( "author2" ); + + List books = new ArrayList<>(); + books.add( "book1" ); + books.add( "book2" ); + Map booksByPublisher = new HashMap<>(); + booksByPublisher.put( "publisher1", "book1" ); + booksByPublisher.put( "publisher2", "book2" ); + Issue3806Mapper.Target target = new Issue3806Mapper.Target( authors, booksByAuthor ); + target.setBooks( books ); + target.setBooksByPublisher( booksByPublisher ); + + Issue3806Mapper.Target source = new Issue3806Mapper.Target( null, null ); + Issue3806Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAuthors() ).containsExactly( "author1", "author2" ); + assertThat( target.getBooksByAuthor() ) + .containsOnly( + entry( "author1", "book1" ), + entry( "author2", "book2" ) + ); + + assertThat( target.getBooks() ).containsExactly( "book1", "book2" ); + assertThat( target.getBooksByPublisher() ) + .containsOnly( + entry( "publisher1", "book1" ), + entry( "publisher2", "book2" ) + ); + + booksByAuthor = new HashMap<>(); + booksByAuthor.put( "author3", "book3" ); + authors = new ArrayList<>(); + authors.add( "author3" ); + + books = new ArrayList<>(); + books.add( "book3" ); + booksByPublisher = new HashMap<>(); + booksByPublisher.put( "publisher3", "book3" ); + source = new Issue3806Mapper.Target( authors, booksByAuthor ); + source.setBooks( books ); + source.setBooksByPublisher( booksByPublisher ); + Issue3806Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAuthors() ).containsExactly( "author3" ); + assertThat( target.getBooksByAuthor() ) + .containsOnly( + entry( "author3", "book3" ) + ); + + assertThat( target.getBooks() ).containsExactly( "book3" ); + assertThat( target.getBooksByPublisher() ) + .containsOnly( + entry( "publisher3", "book3" ) + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java new file mode 100644 index 0000000000..83ee1f32bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Mapper.java @@ -0,0 +1,48 @@ +/* + * 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.test.bugs._3807; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface Issue3807Mapper { + Issue3807Mapper INSTANCE = Mappers.getMapper( Issue3807Mapper.class ); + + TargetWithoutSetter mapNoSetter(Source target); + + NormalTarget mapNormalSource(Source target); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + //CHECKSTYLE:OFF + class TargetWithoutSetter { + public T value; + } + //CHECKSTYLE:ON + + class NormalTarget { + private T value; + + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java new file mode 100644 index 0000000000..db82472f98 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3807/Issue3807Test.java @@ -0,0 +1,32 @@ +/* + * 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.test.bugs._3807; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses(Issue3807Mapper.class) +@IssueKey("3807") +class Issue3807Test { + + @ProcessorTest + void fieldAndSetterShouldWorkWithGeneric() { + Issue3807Mapper.Source source = new Issue3807Mapper.Source( "value" ); + Issue3807Mapper.TargetWithoutSetter targetWithoutSetter = + Issue3807Mapper.INSTANCE.mapNoSetter( source ); + + assertThat( targetWithoutSetter ).isNotNull(); + assertThat( targetWithoutSetter.value ).isEqualTo( "value" ); + + Issue3807Mapper.NormalTarget normalTarget = Issue3807Mapper.INSTANCE.mapNormalSource( source ); + + assertThat( normalTarget ).isNotNull(); + assertThat( normalTarget.getValue() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java new file mode 100644 index 0000000000..10ff9e2628 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Mapper.java @@ -0,0 +1,69 @@ +/* + * 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.test.bugs._3809; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetPropertyName; + +@Mapper +public interface Issue3809Mapper { + void updateMappingFails(Source source, @MappingTarget Target target); + + @Condition + default boolean canMap(Object source, @TargetPropertyName String propertyName) { + return true; + } + + class Source { + private NestedSource param; + + public NestedSource getParam() { + return param; + } + } + + class NestedSource { + private String param1; + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + } + + class Target { + + private NestedTarget param; + + public NestedTarget getParam() { + return param; + } + + public void setParam(NestedTarget param) { + this.param = param; + } + + } + + class NestedTarget { + private String param1; + + public String getParam1() { + return param1; + } + + public void setParam1(String param1) { + this.param1 = param1; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.java new file mode 100644 index 0000000000..23d97b877c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3809/Issue3809Test.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.ap.test.bugs._3809; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@WithClasses(Issue3809Mapper.class) +@IssueKey("3809") +public class Issue3809Test { + + @ProcessorTest + public void shouldCompileNoError() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.java new file mode 100644 index 0000000000..393cb16970 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Child.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.ap.test.bugs._3849; + +public class Child extends Parent { + public Child() { + super(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java new file mode 100644 index 0000000000..e8cdae4d17 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ChildDto.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._3849; + +public class ChildDto extends ParentDto { + public ChildDto(String value) { + super( value ); + } + + public void setValue(String value) { + super.setValue( value ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java new file mode 100644 index 0000000000..68450c7d9d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateBySourceMapper.java @@ -0,0 +1,69 @@ +/* + * 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.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateBySourceMapper { + + DeduplicateBySourceMapper INSTANCE = Mappers.getMapper( DeduplicateBySourceMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ParentDto mapChild(Child source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentSourceInOtherClass" ); + } + + @BeforeMapping + void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingChildSourceInOtherClass" ); + } + + @AfterMapping + void deduplicateBySource(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentSourceInOtherClass" ); + } + + @AfterMapping + void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingChildSourceInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentSource" ); + } + + @BeforeMapping + default void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingChildSource" ); + } + + @AfterMapping + default void deduplicateBySource(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentSource" ); + } + + @AfterMapping + default void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingChildSource" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java new file mode 100644 index 0000000000..0aed8a2957 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateByTargetMapper.java @@ -0,0 +1,69 @@ +/* + * 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.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateByTargetMapper { + + DeduplicateByTargetMapper INSTANCE = Mappers.getMapper( DeduplicateByTargetMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ChildDto mapChild(Parent source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" ); + } + + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTarget" ); + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTarget" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java new file mode 100644 index 0000000000..276a2c4352 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateForCompileArgsMapper.java @@ -0,0 +1,69 @@ +/* + * 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.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateForCompileArgsMapper { + + DeduplicateForCompileArgsMapper INSTANCE = Mappers.getMapper( DeduplicateForCompileArgsMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source, @Context MappingContext context); + + ChildDto mapChild(Parent source, @Context MappingContext context); + + class MappingContext { + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" ); + } + + @BeforeMapping + void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" ); + } + + @AfterMapping + void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" ); + } + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "beforeMappingParentTarget" ); + } + + @BeforeMapping + default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChildTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParentTarget" ); + } + + @AfterMapping + default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChildTarget" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java new file mode 100644 index 0000000000..662137f1a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/DeduplicateGenericMapper.java @@ -0,0 +1,51 @@ +/* + * 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.test.bugs._3849; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DeduplicateGenericMapper { + + DeduplicateGenericMapper INSTANCE = Mappers.getMapper( DeduplicateGenericMapper.class ); + List INVOKED_METHODS = new ArrayList<>(); + + ParentDto mapParent(Parent source); + + ChildDto mapChild(Parent source); + + @BeforeMapping + default void deduplicateBefore(Parent source, @MappingTarget T target) { + INVOKED_METHODS.add( "beforeMappingParentGeneric" ); + } + + @BeforeMapping + default void deduplicateBefore(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "beforeMappingChild" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget T target) { + INVOKED_METHODS.add( "afterMappingGeneric" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget ParentDto target) { + INVOKED_METHODS.add( "afterMappingParent" ); + } + + @AfterMapping + default void deduplicate(Parent source, @MappingTarget ChildDto target) { + INVOKED_METHODS.add( "afterMappingChild" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java new file mode 100644 index 0000000000..a58c52c921 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Issue3849Test.java @@ -0,0 +1,150 @@ +/* + * 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.test.bugs._3849; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("3849") +@WithClasses({ + Parent.class, + ParentDto.class, + Child.class, + ChildDto.class +}) +public class Issue3849Test { + + @ProcessorOption(name = "mapstruct.disableLifecycleOverloadDeduplicateSelector", value = "true") + @ProcessorTest() + @WithClasses(DeduplicateForCompileArgsMapper.class) + void lifecycleMappingOverloadSelectorDisableCompileArgs() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateForCompileArgsMapper.MappingContext mappingContext = + new DeduplicateForCompileArgsMapper.MappingContext(); + ParentDto parentDto = DeduplicateForCompileArgsMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentTargetInOtherClass", + "beforeMappingParentTarget", + "afterMappingParentTargetInOtherClass", + "afterMappingParentTarget" + ); + + DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateForCompileArgsMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTarget", + "beforeMappingChildTarget", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTarget", + "afterMappingChildTarget" + ); + + DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest() + @WithClasses( DeduplicateByTargetMapper.class ) + void lifecycleMappingOverloadByTarget() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateByTargetMapper.MappingContext mappingContext = new DeduplicateByTargetMapper.MappingContext(); + ParentDto parentDto = DeduplicateByTargetMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateByTargetMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentTargetInOtherClass", + "beforeMappingParentTarget", + "afterMappingParentTargetInOtherClass", + "afterMappingParentTarget" + ); + + DeduplicateByTargetMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateByTargetMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateByTargetMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildTargetInOtherClass", + "beforeMappingChildTarget", + "afterMappingChildTargetInOtherClass", + "afterMappingChildTarget" + ); + + DeduplicateByTargetMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest + @WithClasses( DeduplicateBySourceMapper.class ) + void lifecycleMappingOverloadBySource() { + Child child = new Child(); + Parent parent = new Parent(); + + DeduplicateBySourceMapper.MappingContext mappingContext = new DeduplicateBySourceMapper.MappingContext(); + ParentDto parentDto = DeduplicateBySourceMapper.INSTANCE.mapParent( parent, mappingContext ); + assertThat( DeduplicateBySourceMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentSourceInOtherClass", + "beforeMappingParentSource", + "afterMappingParentSourceInOtherClass", + "afterMappingParentSource" + ); + + DeduplicateBySourceMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateBySourceMapper.INSTANCE.mapChild( child, mappingContext ); + + assertThat( DeduplicateBySourceMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChildSourceInOtherClass", + "beforeMappingChildSource", + "afterMappingChildSourceInOtherClass", + "afterMappingChildSource" + ); + + DeduplicateBySourceMapper.INVOKED_METHODS.clear(); + } + + @ProcessorTest + @WithClasses(DeduplicateGenericMapper.class) + void lifecycleMappingOverloadForGeneric() { + Child child = new Child(); + Parent parent = new Parent(); + + ParentDto parentDto = DeduplicateGenericMapper.INSTANCE.mapParent( parent ); + assertThat( DeduplicateGenericMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingParentGeneric", + "afterMappingParent" + ); + + DeduplicateGenericMapper.INVOKED_METHODS.clear(); + + ParentDto childDto = DeduplicateGenericMapper.INSTANCE.mapChild( child ); + + assertThat( DeduplicateGenericMapper.INVOKED_METHODS ) + .containsExactly( + "beforeMappingChild", + "afterMappingChild" + ); + + DeduplicateGenericMapper.INVOKED_METHODS.clear(); + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.java new file mode 100644 index 0000000000..4930677286 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/Parent.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.ap.test.bugs._3849; + +public class Parent { + private String value; + + public Parent() { + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.java new file mode 100644 index 0000000000..098917f001 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3849/ParentDto.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.ap.test.bugs._3849; + +public class ParentDto { + private String value; + + public ParentDto(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.java new file mode 100644 index 0000000000..467b58990c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/DestinationType.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.ap.test.bugs._3884; + +import java.util.List; +import java.util.Map; + +/** + * Destination type interface for testing null value property mapping strategy with Map properties. + */ +public interface DestinationType { + Map getAttributes(); + + void setAttributes(Map attributes); + + List getItems(); + + void setItems(List items); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.java new file mode 100644 index 0000000000..6f6f4f924d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Mapper.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.ap.test.bugs._3884; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing null value property mapping strategy with Map properties. + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) +public interface Issue3884Mapper { + Issue3884Mapper INSTANCE = Mappers.getMapper( Issue3884Mapper.class ); + + void update(@MappingTarget DestinationType destination, SourceType source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java new file mode 100644 index 0000000000..6c789a0c29 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/Issue3884Test.java @@ -0,0 +1,116 @@ +/* + * 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.test.bugs._3884; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +/** + * Test for issue 3884: NullValuePropertyMappingStrategy.SET_TO_DEFAULT should set target Map/Collection to default + * when source and target are all null. + */ +@IssueKey("3884") +@WithClasses({ + DestinationType.class, + SourceType.class, + Issue3884Mapper.class +}) +public class Issue3884Test { + + @ProcessorTest + public void shouldSetTargetToDefaultWhenBothSourceAndTargetAreNull() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + assertThat( source.getAttributes() ).isNull(); + assertThat( target.getAttributes() ).isNull(); + assertThat( source.getItems() ).isNull(); + assertThat( target.getItems() ).isNull(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).isEmpty(); + assertThat( target.getItems() ).isEmpty(); + } + + @ProcessorTest + public void shouldClearTargetWhenSourceIsNullAndTargetIsInitialized() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + Map targetAttributes = new HashMap<>(); + targetAttributes.put( "targetKey", "targetValue" ); + target.setAttributes( targetAttributes ); + + List targetItems = new ArrayList<>(); + targetItems.add( "targetItem" ); + target.setItems( targetItems ); + + assertThat( source.getAttributes() ).isNull(); + assertThat( target.getAttributes() ).isNotEmpty(); + assertThat( source.getItems() ).isNull(); + assertThat( target.getItems() ).isNotEmpty(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).isEmpty(); + assertThat( target.getItems() ).isEmpty(); + } + + @ProcessorTest + public void shouldCopySourceToTargetWhenSourceIsInitializedAndTargetIsNull() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + source.setAttributes( Map.of( "sourceKey", "sourceValue" ) ); + source.setItems( List.of( "sourceItem" ) ); + + assertThat( source.getAttributes() ).isNotEmpty(); + assertThat( target.getAttributes() ).isNull(); + assertThat( source.getItems() ).isNotEmpty(); + assertThat( target.getItems() ).isNull(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) ); + assertThat( target.getItems() ).containsExactly( "sourceItem" ); + } + + @ProcessorTest + public void shouldCopySourceToTargetWhenBothSourceAndTargetAreInitialized() { + DestinationType target = new SourceType(); + SourceType source = new SourceType(); + + source.setAttributes( Map.of( "sourceKey", "sourceValue" ) ); + source.setItems( List.of( "sourceItem" ) ); + + Map targetAttributes = new HashMap<>(); + targetAttributes.put( "targetKey", "targetValue" ); + target.setAttributes( targetAttributes ); + List targetItems = new ArrayList<>(); + targetItems.add( "targetItem" ); + target.setItems( targetItems ); + + assertThat( source.getAttributes() ).isNotEmpty(); + assertThat( target.getAttributes() ).isNotEmpty(); + assertThat( source.getItems() ).isNotEmpty(); + assertThat( target.getItems() ).isNotEmpty(); + + Issue3884Mapper.INSTANCE.update( target, source ); + + assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) ); + assertThat( target.getItems() ).containsExactly( "sourceItem" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.java new file mode 100644 index 0000000000..be5c19e01c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3884/SourceType.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.test.bugs._3884; + +import java.util.List; +import java.util.Map; + +/** + * Source type class implementing DestinationType for testing null value property mapping strategy with Map properties. + */ +public class SourceType implements DestinationType { + private Map attributes; + private List items; + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + @Override + public List getItems() { + return items; + } + + @Override + public void setItems(List items) { + this.items = items; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.java new file mode 100644 index 0000000000..aba305a0bf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Mapper.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.ap.test.bugs._3886.jdk21; + +import java.time.LocalDate; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface Issue3886Mapper { + + RangeRecord map(LocalDate validFrom); + + record RangeRecord(LocalDate validFrom) { + + public RangeRecord restrictTo(RangeRecord other) { + return null; + } + + public void setName(String name) { + // This method is here to ensure that MapStruct won't treat it as a setter + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.java new file mode 100644 index 0000000000..93c068cbc7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3886/jdk21/Issue3886Test.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.ap.test.bugs._3886.jdk21; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.Compiler; + +/** + * @author Filip Hrisafov + */ +@WithClasses(Issue3886Mapper.class) +@IssueKey("3886") +class Issue3886Test { + + // The current version of the Eclipse compiler we use does not support records + @ProcessorTest(Compiler.JDK) + void shouldCompile() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java new file mode 100644 index 0000000000..b646f1a08e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/ErroneousIssue3902Mapper.java @@ -0,0 +1,72 @@ +/* + * 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.test.bugs._3902; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * Mapper for testing bug #3902. + * + * @author znight1020 + */ +@Mapper +public interface ErroneousIssue3902Mapper { + + ErroneousIssue3902Mapper INSTANCE = Mappers.getMapper( ErroneousIssue3902Mapper.class ); + + @Ignored(targets = {"name", "foo", "bar"}) + ZooDto mapWithOneKnownAndMultipleUnknowns(Zoo source); + + @Ignored(targets = {"name", "address", "foo"}) + ZooDto mapWithMultipleKnownAndOneUnknown(Zoo source); + + @Ignored(targets = {"name", "addres"}) + ZooDto mapWithTypo(Zoo source); + + class Zoo { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } + + class ZooDto { + private String name; + private String address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.java new file mode 100644 index 0000000000..44247d8c27 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3902/Issue3902Test.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.ap.test.bugs._3902; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * Verifies that using an unknown property in {@code @Ignored} yields a proper + * compile error instead of an internal processor error. + * + * @author znight1020 + */ +@WithClasses({ErroneousIssue3902Mapper.class}) +@IssueKey("3902") +public class Issue3902Test { + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + // Test case: mapWithOneKnownAndMultipleUnknowns + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" + ), + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 23, + message = "No property named \"bar\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" + ), + + // Test case: mapWithMultipleKnownAndOneUnknown + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 26, + message = "No property named \"foo\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"name\"?" + ), + + // Test case: mapWithTypo + @Diagnostic( + type = ErroneousIssue3902Mapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 29, + message = "No property named \"addres\" exists in @Ignored for target type " + + "\"ErroneousIssue3902Mapper.ZooDto\". Did you mean \"address\"?" + ) + } + ) + public void shouldFailOnUnknownPropertiesInIgnored() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.java new file mode 100644 index 0000000000..27e3ac9960 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Mapper.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.ap.test.bugs._3905; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface Issue3905Mapper { + + OverrideDto map(Override override); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.java new file mode 100644 index 0000000000..f15271362d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Issue3905Test.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.ap.test.bugs._3905; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + */ +@IssueKey("3905") +@WithClasses({ + Issue3905Mapper.class, + Override.class, + OverrideDto.class +}) +class Issue3905Test { + + @ProcessorTest + void shouldCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.java new file mode 100644 index 0000000000..dedc6b28dc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/Override.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.ap.test.bugs._3905; + +/** + * @author Filip Hrisafov + */ +public class Override { + + private final String name; + + public Override(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.java new file mode 100644 index 0000000000..531fab505b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3905/OverrideDto.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.ap.test.bugs._3905; + +/** + * @author Filip Hrisafov + */ +public class OverrideDto { + + private final String name; + + public OverrideDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/SameClassNameInDifferentPackageTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/SameClassNameInDifferentPackageTest.java index d709ebe04b..78cbc6177e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/SameClassNameInDifferentPackageTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/SameClassNameInDifferentPackageTest.java @@ -1,35 +1,20 @@ -/** - * Copyright 2012-2015 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._394; -import org.junit.Test; -import org.junit.runner.RunWith; +import java.util.HashMap; +import java.util.Map; + import org.mapstruct.ap.test.bugs._394.source.AnotherCar; import org.mapstruct.ap.test.bugs._394.source.Cars; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; - -import java.util.HashMap; -import java.util.Map; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @WithClasses( { SameNameForSourceAndTargetCarsMapper.class, @@ -39,10 +24,9 @@ org.mapstruct.ap.test.bugs._394._target.AnotherCar.class } ) @IssueKey("394") -@RunWith(AnnotationProcessorTestRunner.class) public class SameClassNameInDifferentPackageTest { - @Test + @ProcessorTest public void shouldCreateMapMethodImplementation() { Map values = new HashMap(); //given diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/SameNameForSourceAndTargetCarsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/SameNameForSourceAndTargetCarsMapper.java index 0902001576..1d43087a51 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/SameNameForSourceAndTargetCarsMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/SameNameForSourceAndTargetCarsMapper.java @@ -1,23 +1,13 @@ -/** - * Copyright 2012-2015 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._394; +import java.util.List; + +import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @@ -25,17 +15,13 @@ import org.mapstruct.ap.test.bugs._394.source.Cars; import org.mapstruct.factory.Mappers; -import java.util.List; - -import org.mapstruct.InheritInverseConfiguration; - @Mapper public interface SameNameForSourceAndTargetCarsMapper { SameNameForSourceAndTargetCarsMapper INSTANCE = Mappers.getMapper( SameNameForSourceAndTargetCarsMapper.class ); @Mappings({ - @Mapping(source = "numberOfSeats", target = "seatCount") + @Mapping(target = "seatCount", source = "numberOfSeats") }) AnotherCar sourceCarToTargetCar(org.mapstruct.ap.test.bugs._394.source.AnotherCar car); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/_target/AnotherCar.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/_target/AnotherCar.java index 540a9da0a1..bd99d6f524 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/_target/AnotherCar.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/_target/AnotherCar.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._394._target; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/_target/Cars.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/_target/Cars.java index 62ed52acc9..185e52e046 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/_target/Cars.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/_target/Cars.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._394._target; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/source/AnotherCar.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/source/AnotherCar.java index 3a235748a2..0d050196e5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/source/AnotherCar.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/source/AnotherCar.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._394.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/source/Cars.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/source/Cars.java index 3e9627eb37..68bac36095 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/source/Cars.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_394/source/Cars.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._394.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Mapper.java new file mode 100644 index 0000000000..ab053bc44e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Mapper.java @@ -0,0 +1,93 @@ +/* + * 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.test.bugs._3948; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface Issue3948Mapper { + + class ComparableField implements Comparable { + + private final String code; + + public ComparableField(String code) { + this.code = code; + } + + @Override + public int compareTo(ComparableField o) { + return code.compareTo( o.code ); + } + } + + class Id implements Comparable> { + private final long value; + + public Id(long value) { + this.value = value; + } + + @Override + public int compareTo(Id o) { + return Long.compare( value, o.value ); + } + } + + class From { + private long id; + private String code; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + } + + class To { + private Id id; + private ComparableField comparableField; + + public Id getId() { + return id; + } + + public void setId(Id id) { + this.id = id; + } + + public ComparableField getComparableField() { + return comparableField; + } + + public void setComparableField(ComparableField comparableField) { + this.comparableField = comparableField; + } + } + + @Mapping(source = "code", target = "comparableField") + To convert(From from); + + default Id longToId(Long id) { + return id == null ? null : new Id<>(id); + } + + default ComparableField convert(String code) { + return code == null ? null : new ComparableField(code); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Test.java new file mode 100644 index 0000000000..8c80b2e802 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Test.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.ap.test.bugs._3948; + +import org.junitpioneer.jupiter.Issue; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author hduelme + */ +@Issue("3948") +public class Issue3948Test { + + @ProcessorTest + @WithClasses(Issue3948Mapper.class) + public void shouldRequireExactGenericMatchForDeclaredSameTypes() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java new file mode 100644 index 0000000000..e542c388d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/DateSource.java @@ -0,0 +1,14 @@ +/* + * 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.test.bugs._3949; + +import java.time.LocalDate; + +public class DateSource { + public LocalDate getDate() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.java new file mode 100644 index 0000000000..135aa79056 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949ClassMapper.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.ap.test.bugs._3949; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL) +public interface Issue3949ClassMapper { + + Issue3949ClassMapper INSTANCE = Mappers.getMapper( Issue3949ClassMapper.class ); + + void overwriteDate(@MappingTarget TargetDate target, DateSource dateSource); + + void overwriteString(@MappingTarget TargetString target, StringSource stringSource); + + void overwriteDateWithConversion(@MappingTarget TargetDate target, StringSource dateSource); + + void overwriteStringWithConversion(@MappingTarget TargetString target, DateSource stringSource); + + void updateParent(@MappingTarget ParentTarget target, ParentSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.java new file mode 100644 index 0000000000..c25041ad2f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949InterfaceMapper.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.ap.test.bugs._3949; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL) +public interface Issue3949InterfaceMapper { + + Issue3949InterfaceMapper INSTANCE = Mappers.getMapper( Issue3949InterfaceMapper.class ); + + void overwriteDate(@MappingTarget TargetDateInterface target, DateSource dateSource); + + void overwriteString(@MappingTarget TargetStringInterface target, StringSource stringSource); + + void overwriteDateWithConversion(@MappingTarget TargetDateInterface target, StringSource dateSource); + + void overwriteStringWithConversion(@MappingTarget TargetStringInterface target, DateSource stringSource); + + void updateParent(@MappingTarget ParentTargetInterface target, ParentSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java new file mode 100644 index 0000000000..b6abee819c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/Issue3949Test.java @@ -0,0 +1,94 @@ +/* + * 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.test.bugs._3949; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests if overloaded targets are correctly cast when set to null + * + * @author hduelme + */ +@IssueKey("3949") +@WithClasses({ + ParentSource.class, + ParentTargetInterface.class, + ParentTarget.class, + StringSource.class, + TargetStringInterface.class, + TargetString.class, + DateSource.class, + TargetDateInterface.class, + TargetDate.class +}) +public class Issue3949Test { + + @ProcessorTest + @WithClasses({ + Issue3949ClassMapper.class + }) + void shouldCompileAndSetCorrectlyToNullForClass() { + TargetDate shouldSetDateToNull = new TargetDate(); + Issue3949ClassMapper.INSTANCE.overwriteDate( shouldSetDateToNull, new DateSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + shouldSetDateToNull = new TargetDate(); + Issue3949ClassMapper.INSTANCE.overwriteDateWithConversion( shouldSetDateToNull, new StringSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + TargetString shouldSetStringToNull = new TargetString(); + Issue3949ClassMapper.INSTANCE.overwriteString( shouldSetStringToNull, new StringSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + shouldSetStringToNull = new TargetString(); + Issue3949ClassMapper.INSTANCE.overwriteStringWithConversion( shouldSetStringToNull, new DateSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + ParentTarget parentTarget = new ParentTarget(); + parentTarget.setChild( new ParentTarget() ); + Issue3949ClassMapper.INSTANCE.updateParent( parentTarget, new ParentSource() ); + assertThat( parentTarget.getChild() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + Issue3949InterfaceMapper.class + }) + void shouldCompileAndSetCorrectlyToNullForInterface() { + TargetDateInterface shouldSetDateToNull = new TargetDate(); + Issue3949InterfaceMapper.INSTANCE.overwriteDate( shouldSetDateToNull, new DateSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + shouldSetDateToNull = new TargetDate(); + Issue3949InterfaceMapper.INSTANCE.overwriteDateWithConversion( shouldSetDateToNull, new StringSource() ); + assertThat( shouldSetDateToNull.getString() ).isNotNull(); + assertThat( shouldSetDateToNull.getDate() ).isNull(); + + TargetStringInterface shouldSetStringToNull = new TargetString(); + Issue3949InterfaceMapper.INSTANCE.overwriteString( shouldSetStringToNull, new StringSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + shouldSetStringToNull = new TargetString(); + Issue3949InterfaceMapper.INSTANCE.overwriteStringWithConversion( shouldSetStringToNull, new DateSource() ); + assertThat( shouldSetStringToNull.getDate() ).isNull(); + assertThat( shouldSetStringToNull.getDateValue() ).isNotNull(); + + ParentTargetInterface parentTarget = new ParentTarget(); + parentTarget.setChild( new ParentTarget() ); + Issue3949InterfaceMapper.INSTANCE.updateParent( parentTarget, new ParentSource() ); + assertThat( parentTarget.getChild() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.java new file mode 100644 index 0000000000..551501aa24 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentSource.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.ap.test.bugs._3949; + +public class ParentSource { + public ParentSource getChild() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.java new file mode 100644 index 0000000000..3d8ca3b51b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTarget.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.ap.test.bugs._3949; + +public class ParentTarget implements ParentTargetInterface { + private ParentTarget child; + + @Override + public ParentTarget getChild() { + return child; + } + + @Override + public void setChild(String child) { + throw new IllegalArgumentException(); + } + + @Override + public void setChild(ParentTarget child) { + this.child = child; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java new file mode 100644 index 0000000000..5311fb4c9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/ParentTargetInterface.java @@ -0,0 +1,14 @@ +/* + * 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.test.bugs._3949; + +public interface ParentTargetInterface { + ParentTarget getChild(); + + void setChild(String child); + + void setChild(ParentTarget child); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.java new file mode 100644 index 0000000000..11ba1419d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/StringSource.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.ap.test.bugs._3949; + +public class StringSource { + public String getDate() { + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.java new file mode 100644 index 0000000000..85b12a618d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDate.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.ap.test.bugs._3949; + +import java.time.LocalDate; + +public class TargetDate implements TargetDateInterface { + private LocalDate date = LocalDate.now(); + private String string = ""; + + @Override + public void setDate(LocalDate date) { + this.date = date; + } + + @Override + public void setDate(String date) { + this.string = date; + } + + @Override + public LocalDate getDate() { + return date; + } + + @Override + public String getString() { + return string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.java new file mode 100644 index 0000000000..df28936914 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetDateInterface.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.ap.test.bugs._3949; + +import java.time.LocalDate; + +public interface TargetDateInterface { + void setDate(LocalDate date); + + void setDate(String date); + + LocalDate getDate(); + + String getString(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.java new file mode 100644 index 0000000000..b8cbbb2bbb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetString.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.test.bugs._3949; + +import java.time.LocalDate; + +public class TargetString implements TargetStringInterface { + private LocalDate date = LocalDate.now(); + private String string = ""; + + @Override + public void setDate(LocalDate date) { + this.date = date; + } + + @Override + public void setDate(String date) { + this.string = date; + } + + @Override + public String getDate() { + return string; + } + + @Override + public LocalDate getDateValue() { + return date; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.java new file mode 100644 index 0000000000..dd82231a51 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3949/TargetStringInterface.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.ap.test.bugs._3949; + +import java.time.LocalDate; + +public interface TargetStringInterface { + void setDate(LocalDate date); + + void setDate(String date); + + String getDate(); + + LocalDate getDateValue(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/EntityFactory.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/EntityFactory.java index e1783da251..77811f4ad9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/EntityFactory.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/EntityFactory.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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._405; - import org.mapstruct.TargetType; public class EntityFactory { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Issue405Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Issue405Test.java index 94025316ac..f1f34ee394 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Issue405Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Issue405Test.java @@ -1,28 +1,13 @@ -/** - * Copyright 2012-2015 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.bugs._405; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/405. @@ -30,10 +15,9 @@ * @author Sjaak Derksen */ @IssueKey( "405" ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue405Test { - @Test + @ProcessorTest @WithClasses( { EntityFactory.class, Person.class, People.class, PersonMapper.class } ) public void shouldGenerateFactoryCorrectMethodForIterables() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/People.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/People.java index af95f529f2..87e7ab8818 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/People.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/People.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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._405; - import java.util.List; public class People { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Person.java index 77ab887aff..36c119cd41 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Person.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Person.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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._405; - import java.util.List; public class Person { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/PersonMapper.java index 1ce8ac07da..4be1356aed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/PersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/PersonMapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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._405; import java.util.List; import java.util.Map; + import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @@ -31,7 +19,7 @@ public abstract class PersonMapper { public static final PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); @Mappings( { - @Mapping( source = "id", target = "name" ) } + @Mapping(target = "name", source = "id") } ) abstract People personToPeople(Person person); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/ErroneousSetToDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/ErroneousSetToDefaultMapper.java new file mode 100644 index 0000000000..4ede382781 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/ErroneousSetToDefaultMapper.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.ap.test.bugs._4060; + +import java.math.BigDecimal; +import java.time.LocalDate; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; + +@Mapper +public interface ErroneousSetToDefaultMapper { + + class WithLocalDate { + private LocalDate value; + + public LocalDate getValue() { + return value; + } + + public void setValue(LocalDate value) { + this.value = value; + } + } + + class WithBigDecimal { + private BigDecimal value; + + public BigDecimal getValue() { + return value; + } + + public void setValue(BigDecimal value) { + this.value = value; + } + } + + class WithComparable { + private Comparable value; + + public Comparable getValue() { + return value; + } + + public void setValue(Comparable value) { + this.value = value; + } + } + + abstract class AbstractValue { + } + + class WithAbstractValue { + private AbstractValue value; + + public AbstractValue getValue() { + return value; + } + + public void setValue(AbstractValue value) { + this.value = value; + } + } + + enum EnumValue { + FIRST + } + + class WithEnumValue { + private EnumValue value; + + public EnumValue getValue() { + return value; + } + + public void setValue(EnumValue value) { + this.value = value; + } + } + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateLocalDate(@MappingTarget WithLocalDate target, WithLocalDate source); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateBigDecimal(@MappingTarget WithBigDecimal target, WithBigDecimal source); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateComparable(@MappingTarget WithComparable target, WithComparable source); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateAbstractValue(@MappingTarget WithAbstractValue target, WithAbstractValue source); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateEnumValue(@MappingTarget WithEnumValue target, WithEnumValue source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/Issue4060Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/Issue4060Test.java new file mode 100644 index 0000000000..e0e51dac32 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/Issue4060Test.java @@ -0,0 +1,77 @@ +/* + * 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.test.bugs._4060; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("4060") +public class Issue4060Test { + + @ProcessorTest + @WithClasses(ErroneousSetToDefaultMapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 87, + message = "LocalDate does not have an accessible parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression."), + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 90, + message = "BigDecimal does not have an accessible parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression."), + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 93, + message = "Comparable does not have an accessible parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression."), + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 96, + message = "ErroneousSetToDefaultMapper.AbstractValue does not have an accessible " + + "parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression."), + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 99, + message = "ErroneousSetToDefaultMapper.EnumValue does not have an accessible " + + "parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression.") + }) + void setToDefaultWithoutParameterlessConstructorFails() { + } + + @ProcessorTest + @WithClasses(SetToDefaultMapper.class) + void setToDefaultWithParameterlessConstructorResetsToNewInstance() { + SetToDefaultMapper.Target target = new SetToDefaultMapper.Target(); + SetToDefaultMapper.Nested existing = new SetToDefaultMapper.Nested(); + existing.setName( "existing" ); + target.setNested( existing ); + target.setText( "existing" ); + + SetToDefaultMapper.INSTANCE.update( target, new SetToDefaultMapper.Source() ); + + // source properties are null, SET_TO_DEFAULT resets them to a fresh default + assertThat( target.getNested() ).isNotNull(); + assertThat( target.getNested() ).isNotSameAs( existing ); + assertThat( target.getNested().getName() ).isNull(); + assertThat( target.getText() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/SetToDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/SetToDefaultMapper.java new file mode 100644 index 0000000000..edfe7e9b3b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/SetToDefaultMapper.java @@ -0,0 +1,75 @@ +/* + * 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.test.bugs._4060; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SetToDefaultMapper { + + SetToDefaultMapper INSTANCE = Mappers.getMapper( SetToDefaultMapper.class ); + + class Nested { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class Target { + private Nested nested; + private String text; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + + class Source { + private Nested nested; + private String text; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void update(@MappingTarget Target target, Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Mapper.java index c68dfb2c39..32cf02e3af 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Mapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._513; @@ -26,7 +13,6 @@ public interface Issue513Mapper { Issue513Mapper INSTANCE = Mappers.getMapper( Issue513Mapper.class ); - Target map(Source source) throws MappingException, MappingValueException, MappingKeyException; TargetElement mapElement(SourceElement source) throws MappingException; @@ -34,5 +20,4 @@ public interface Issue513Mapper { TargetKey mapKey(SourceKey source) throws MappingException, MappingKeyException; TargetValue mapValue(SourceValue source) throws MappingException, MappingValueException; - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Test.java index 39d809e945..9bc5be096e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Test.java @@ -1,30 +1,18 @@ -/** - * Copyright 2012-2015 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.bugs._513; import java.util.Arrays; import java.util.HashMap; -import org.junit.Test; -import org.junit.runner.RunWith; + import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/513. @@ -46,63 +34,66 @@ MappingKeyException.class, MappingValueException.class } ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue513Test { - @Test( expected = MappingException.class ) + @ProcessorTest public void shouldThrowMappingException() throws Exception { Source source = new Source(); SourceElement sourceElement = new SourceElement(); sourceElement.setValue( "test" ); - source.setCollection( Arrays.asList( new SourceElement[]{ sourceElement } ) ); + source.setCollection( Arrays.asList( sourceElement ) ); - Issue513Mapper.INSTANCE.map( source ); + assertThatThrownBy( () -> Issue513Mapper.INSTANCE.map( source ) ) + .isInstanceOf( MappingException.class ); } - @Test( expected = MappingKeyException.class ) + @ProcessorTest public void shouldThrowMappingKeyException() throws Exception { Source source = new Source(); SourceKey sourceKey = new SourceKey(); sourceKey.setValue( MappingKeyException.class.getSimpleName() ); SourceValue sourceValue = new SourceValue(); - HashMap map = new HashMap(); + HashMap map = new HashMap<>(); map.put( sourceKey, sourceValue ); source.setMap( map ); - Issue513Mapper.INSTANCE.map( source ); + assertThatThrownBy( () -> Issue513Mapper.INSTANCE.map( source ) ) + .isInstanceOf( MappingKeyException.class ); } - @Test( expected = MappingValueException.class ) + @ProcessorTest public void shouldThrowMappingValueException() throws Exception { Source source = new Source(); SourceKey sourceKey = new SourceKey(); SourceValue sourceValue = new SourceValue(); sourceValue.setValue( MappingValueException.class.getSimpleName() ); - HashMap map = new HashMap(); + HashMap map = new HashMap<>(); map.put( sourceKey, sourceValue ); source.setMap( map ); - Issue513Mapper.INSTANCE.map( source ); + assertThatThrownBy( () -> Issue513Mapper.INSTANCE.map( source ) ) + .isInstanceOf( MappingValueException.class ); } - @Test( expected = MappingException.class ) + @ProcessorTest public void shouldThrowMappingCommonException() throws Exception { Source source = new Source(); SourceKey sourceKey = new SourceKey(); SourceValue sourceValue = new SourceValue(); sourceValue.setValue( MappingException.class.getSimpleName() ); - HashMap map = new HashMap(); + HashMap map = new HashMap<>(); map.put( sourceKey, sourceValue ); source.setMap( map ); - Issue513Mapper.INSTANCE.map( source ); + assertThatThrownBy( () -> Issue513Mapper.INSTANCE.map( source ) ) + .isInstanceOf( MappingException.class ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingException.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingException.java index 0b37d77101..289e86abbd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingException.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingException.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.bugs._513; @@ -24,7 +11,6 @@ */ public class MappingException extends Exception { - public MappingException() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingKeyException.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingKeyException.java index 429ab2ab3f..668667fb07 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingKeyException.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingKeyException.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.bugs._513; @@ -24,7 +11,6 @@ */ public class MappingKeyException extends Exception { - public MappingKeyException() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingValueException.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingValueException.java index 1358b7cf8b..9bf51c2fdd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingValueException.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/MappingValueException.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.bugs._513; @@ -24,7 +11,6 @@ */ public class MappingValueException extends Exception { - public MappingValueException() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Source.java index c70fec8f5c..1e85ffe808 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._513; @@ -30,7 +17,6 @@ public class Source { private Collection collection; private Map map; - public Collection getCollection() { return collection; } @@ -46,5 +32,4 @@ public Map getMap() { public void setMap(Map map) { this.map = map; } - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceElement.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceElement.java index b985cc6e89..329b502361 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceElement.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceElement.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._513; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceKey.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceKey.java index f6aa462523..9490e95d61 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceKey.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceKey.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._513; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceValue.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceValue.java index 2b2fc09955..fbdde5a474 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceValue.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/SourceValue.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._513; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Target.java index c384e1b56e..7356849403 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._513; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetElement.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetElement.java index 3150984b04..532c44052b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetElement.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetElement.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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.test.bugs._513; - /** * * @author Sjaak Derksen diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetKey.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetKey.java index 6c7f32bf66..aef8b8ece1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetKey.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetKey.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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.test.bugs._513; - /** * * @author Sjaak Derksen diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetValue.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetValue.java index 63726457d8..beda1c1389 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetValue.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetValue.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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.test.bugs._513; - /** * * @author Sjaak Derksen diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Mapper.java index 6b18ae6cb6..5baaa475d1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Mapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._515; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Test.java index 66b4113749..eb32a41a85 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Issue515Test.java @@ -1,28 +1,13 @@ -/** - * Copyright 2012-2015 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.bugs._515; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/515. @@ -30,10 +15,9 @@ * @author Sjaak Derksen */ @IssueKey( "515" ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue515Test { - @Test + @ProcessorTest @WithClasses( { Issue515Mapper.class, Source.class, Target.class } ) public void shouldIgnoreParanthesesOpenInStringDefinition() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Source.java index eab49fd99a..c21d940f63 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._515; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Target.java index d20d6ade15..bfb27f7276 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_515/Target.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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._515; - public class Target { private String id; @@ -30,5 +16,4 @@ public String getId() { public void setId(String id) { this.id = id; } - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Issue516Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Issue516Test.java index 27f1413730..226a7f8157 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Issue516Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Issue516Test.java @@ -1,29 +1,15 @@ -/** - * Copyright 2012-2015 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.test.bugs._516; -import static org.fest.assertions.Assertions.assertThat; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/516. @@ -31,10 +17,9 @@ * @author Sjaak Derksen */ @IssueKey( "516" ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue516Test { - @Test + @ProcessorTest @WithClasses( { SourceTargetMapper.class, Source.class, Target.class } ) public void shouldAddNullPtrCheckAroundSourceForAdder() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Source.java index adf2b9af84..593522d4a7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._516; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/SourceTargetMapper.java index ee2d7429b6..788ed39c89 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.bugs._516; @@ -22,7 +9,6 @@ import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; - /** * * @author Sjaak Derksen diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Target.java index 0f0c0ea410..1b65b0662b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._516; @@ -39,7 +26,7 @@ public void setElements(List elements) { public void addElement(String element) { if ( elements == null ) { - elements = new ArrayList(); + elements = new ArrayList<>(); } elements.add( element ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537Mapper.java new file mode 100644 index 0000000000..3651d41ba6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537Mapper.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.ap.test.bugs._537; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Bandowski + */ +@Mapper(uses = ReferenceMapper.class, config = Issue537MapperConfig.class) +public interface Issue537Mapper { + Issue537Mapper INSTANCE = Mappers.getMapper( Issue537Mapper.class ); + + Target mapDto(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537MapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537MapperConfig.java new file mode 100644 index 0000000000..14a60f7b36 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537MapperConfig.java @@ -0,0 +1,16 @@ +/* + * 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.test.bugs._537; + +import org.mapstruct.MapperConfig; + +/** + * @author Christian Bandowski + */ +@MapperConfig(uses = ReferenceMapper.class) +public interface Issue537MapperConfig { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537Test.java new file mode 100644 index 0000000000..6d60aaa79e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537Test.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.test.bugs._537; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Bandowski + */ +@IssueKey("537") +@WithClasses({ + Issue537Mapper.class, + Issue537MapperConfig.class, + ReferenceMapper.class, + Source.class, + Target.class +}) +public class Issue537Test { + + @ProcessorTest + public void testThatReferencedMapperWillBeUsed() { + Target target = Issue537Mapper.INSTANCE.mapDto( new Source( "abc" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( 3 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/ReferenceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/ReferenceMapper.java new file mode 100644 index 0000000000..86f9554c86 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/ReferenceMapper.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.ap.test.bugs._537; + +/** + * @author Christian Bandowski + */ +public class ReferenceMapper { + public Integer stringLength(String source) { + return source == null ? null : source.length(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Source.java new file mode 100644 index 0000000000..dd1040dafb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Source.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.ap.test.bugs._537; + +/** + * @author Christian Bandowski + */ +public class Source { + private String value; + + public Source() { + } + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Target.java new file mode 100644 index 0000000000..4a95e4aeed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Target.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.ap.test.bugs._537; + +/** + * @author Christian Bandowski + */ +public class Target { + private Integer value; + + public Target() { + } + + public Target(Integer value) { + this.value = value; + } + + public Integer getValue() { + return value; + } + + public void setValue(Integer value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/Issue543Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/Issue543Mapper.java new file mode 100644 index 0000000000..1a22879c38 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/Issue543Mapper.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.ap.test.bugs._543; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.bugs._543.dto.Source; +import org.mapstruct.ap.test.bugs._543.dto.Target; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = { SourceUtil.class }) +public interface Issue543Mapper { + + List map(List source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/Issue543Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/Issue543Test.java new file mode 100644 index 0000000000..4a6f0176d1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/Issue543Test.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.test.bugs._543; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.bugs._543.dto.Source; +import org.mapstruct.ap.test.bugs._543.dto.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Filip Hrisafov + */ +@IssueKey("543") +@WithClasses({ + Issue543Mapper.class, + Source.class, + SourceUtil.class, + Target.class +}) +public class Issue543Test { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldCompile() { + generatedSource.forMapper( Issue543Mapper.class ).containsImportFor( Source.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/SourceUtil.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/SourceUtil.java new file mode 100644 index 0000000000..ef0e7879e4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/SourceUtil.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.ap.test.bugs._543; + +import org.mapstruct.ap.test.bugs._543.dto.Source; +import org.mapstruct.ap.test.bugs._543.dto.Target; + +/** + * @author Filip Hrisafov + */ +public class SourceUtil { + + private SourceUtil() { + + } + + public static Target from(Source source) { + if ( source == null ) { + return null; + } + return new Target( source.getString() ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/dto/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/dto/Source.java new file mode 100644 index 0000000000..2a996f1fe9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/dto/Source.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.ap.test.bugs._543.dto; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private String string; + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/dto/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/dto/Target.java new file mode 100644 index 0000000000..c868716db6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_543/dto/Target.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.ap.test.bugs._543.dto; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String string; + + public Target(String string) { + this.string = string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Issue577Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Issue577Test.java index 96f9a4c371..198231f410 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Issue577Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Issue577Test.java @@ -1,37 +1,21 @@ -/** - * Copyright 2012-2015 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._577; -import static org.fest.assertions.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; @IssueKey( "577" ) @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue577Test { - @Test + @ProcessorTest public void shouldMapTwoArraysToCollections() { Source source = new Source(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Source.java index 31b899b3d8..80dd1d6408 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._577; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/SourceTargetMapper.java index 1a832077f1..7b4987c996 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._577; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Target.java index cb7ee171ff..7646439a66 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_577/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._577; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Issue580Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Issue580Test.java deleted file mode 100644 index e2ab0bec53..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Issue580Test.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.bugs._580; - -import java.time.LocalDate; - -import javax.xml.datatype.DatatypeConstants; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.XMLGregorianCalendar; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mapstruct.ap.testutil.IssueKey; -import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; - -import static org.fest.assertions.Assertions.assertThat; - -/** - * @author Andreas Gudian - */ -@IssueKey("580") -@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) -public class Issue580Test { - - @Test - public void shouldNullCheckOnBuiltinAndConversion() { - Target target = SourceTargetMapper.INSTANCE.toTarget( new Source() ); - - assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isNull(); - - Source source = SourceTargetMapper.INSTANCE.toSource( new Target() ); - - assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isNull(); - } - - @Test - public void shouldMapCorrectlyOnBuiltinAndConversion() throws Exception { - XMLGregorianCalendar calendarDate = DatatypeFactory.newInstance().newXMLGregorianCalendarDate( - 2007, - 11, - 14, - DatatypeConstants.FIELD_UNDEFINED ); - - LocalDate localDate = LocalDate.of( 2007, 11, 14 ); - - Source s1 = new Source(); - s1.setDate( calendarDate ); - Target target = SourceTargetMapper.INSTANCE.toTarget( s1 ); - - assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( localDate ); - - - Target t1 = new Target(); - t1.setDate( localDate ); - Source source = SourceTargetMapper.INSTANCE.toSource( t1 ); - - assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isEqualTo( calendarDate ); - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Source.java deleted file mode 100644 index c423415fcd..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Source.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.bugs._580; - -import javax.xml.datatype.XMLGregorianCalendar; - - -/** - * @author Andreas Gudian - */ -public class Source { - private XMLGregorianCalendar date; - - public XMLGregorianCalendar getDate() { - return date; - } - - public void setDate(XMLGregorianCalendar date) { - this.date = date; - } - -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/SourceTargetMapper.java deleted file mode 100644 index b9b9f9bd40..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/SourceTargetMapper.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.bugs._580; - -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -/** - * @author Andreas Gudian - */ -@Mapper -public interface SourceTargetMapper { - - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - - Target toTarget(Source source); - - Source toSource(Target target); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Target.java deleted file mode 100644 index ee2dab0cf4..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_580/Target.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.bugs._580; - -import java.time.LocalDate; - -/** - * @author Andreas Gudian - */ -public class Target { - - private LocalDate date; - - public LocalDate getDate() { - return date; - } - - public void setDate(LocalDate date) { - this.date = date; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/Issue581Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/Issue581Test.java index 52b065cbf5..7c1dfb266e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/Issue581Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/Issue581Test.java @@ -1,38 +1,22 @@ -/** - * Copyright 2012-2015 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._581; -import static org.fest.assertions.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.bugs._581.source.Car; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; @IssueKey( "581" ) @WithClasses({ Car.class, org.mapstruct.ap.test.bugs._581._target.Car.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue581Test { - @Test + @ProcessorTest public void shouldMapSourceAndTargetWithTheSameClassName() { Car source = new Car(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/SourceTargetMapper.java index 1f3c664aae..4619412738 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/SourceTargetMapper.java @@ -1,29 +1,16 @@ -/** - * Copyright 2012-2015 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._581; +import java.util.List; + import org.mapstruct.Mapper; import org.mapstruct.ap.test.bugs._581.source.Car; import org.mapstruct.factory.Mappers; -import java.util.List; - @Mapper public interface SourceTargetMapper { diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/_target/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/_target/Car.java index 6a8d9a1b9d..cf472286b8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/_target/Car.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/_target/Car.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._581._target; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/source/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/source/Car.java index 4de5cae6f1..b5289a3492 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/source/Car.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/source/Car.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._581.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/ErroneousSourceTargetMapper.java new file mode 100644 index 0000000000..0991468293 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/ErroneousSourceTargetMapper.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.ap.test.bugs._590; + +import java.util.logging.XMLFormatter; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN) +public abstract class ErroneousSourceTargetMapper { + public static final ErroneousSourceTargetMapper INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper.class ); + + public abstract void sourceToTarget(@MappingTarget Target target, Source source); + + public T unused(String string, @TargetType Class clazz) { + throw new RuntimeException( "should never be called" ); + } + + public static class Source { + private String prop; + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } + } + + public static class Target { + private XMLFormatter prop; + + public XMLFormatter getProp() { + return prop; + } + + public void setProp(XMLFormatter prop) { + this.prop = prop; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/Issue590Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/Issue590Test.java index 9e6ecdf33d..0bf8553e15 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/Issue590Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/Issue590Test.java @@ -1,45 +1,32 @@ -/** - * Copyright 2012-2015 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.bugs._590; import javax.tools.Diagnostic.Kind; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * @author Andreas Gudian */ -@RunWith(AnnotationProcessorTestRunner.class) -@WithClasses(SourceTargetMapper.class) +@WithClasses(ErroneousSourceTargetMapper.class) public class Issue590Test { - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, - diagnostics = { @Diagnostic(type = SourceTargetMapper.class, - kind = Kind.ERROR, - messageRegExp = "Can't map property \"java\\.lang\\.String prop\" to \"[^ ]+ prop\"") }) + diagnostics = { + @Diagnostic(type = ErroneousSourceTargetMapper.class, + kind = Kind.ERROR, + message = "Can't map property \"String prop\" to \"XMLFormatter prop\". " + + "Consider to declare/implement a mapping method: \"XMLFormatter map(String value)\".") + }) public void showsCantMapPropertyError() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/SourceTargetMapper.java deleted file mode 100644 index 107ed2af9e..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/SourceTargetMapper.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.bugs._590; - -import java.util.logging.XMLFormatter; - -import org.mapstruct.Mapper; -import org.mapstruct.MappingTarget; -import org.mapstruct.ReportingPolicy; -import org.mapstruct.TargetType; -import org.mapstruct.factory.Mappers; - -/** - * @author Andreas Gudian - */ -@Mapper(unmappedTargetPolicy = ReportingPolicy.WARN) -public abstract class SourceTargetMapper { - public static final SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - - public abstract void sourceToTarget(@MappingTarget Target target, Source source); - - public T unused(String string, @TargetType Class clazz) { - throw new RuntimeException( "should never be called" ); - } - - public static class Source { - private String prop; - - public String getProp() { - return prop; - } - - public void setProp(String prop) { - this.prop = prop; - } - } - - public static class Target { - private XMLFormatter prop; - - public XMLFormatter getProp() { - return prop; - } - - public void setProp(XMLFormatter prop) { - this.prop = prop; - } - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java new file mode 100644 index 0000000000..b77da90ad9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.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.test.bugs._611; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Tillmann Gaida + */ +@IssueKey("611") +@WithClasses({ + SomeClass.class, + SomeOtherClass.class +}) +public class Issue611Test { + /** + * Checks if an implementation of a nested mapper can be loaded at all. + */ + @ProcessorTest + public void mapperIsFound() { + assertThat( SomeClass.InnerMapper.INSTANCE ).isNotNull(); + } + + /** + * Checks if an implementation of a nested mapper can be loaded which is nested into an already + * nested class. + */ + @ProcessorTest + public void mapperNestedInsideNestedClassIsFound() { + assertThat( SomeClass.SomeInnerClass.InnerMapper.INSTANCE ).isNotNull(); + } + + /** + * Checks if it is possible to load two mapper implementations which have equal simple names + * in the same package. + */ + @ProcessorTest + public void rightMapperIsFound() { + SomeClass.InnerMapper.Source source1 = new SomeClass.InnerMapper.Source( "test" ); + SomeOtherClass.InnerMapper.Source source2 = new SomeOtherClass.InnerMapper.Source( "test2" ); + + SomeClass.InnerMapper.Target target1 = SomeClass.InnerMapper.INSTANCE.toTarget( source1 ); + SomeOtherClass.InnerMapper.Target target2 = SomeOtherClass.InnerMapper.INSTANCE.toTarget( source2 ); + + assertThat( target1 ).isExactlyInstanceOf( SomeClass.InnerMapper.Target.class ); + assertThat( target2 ).isExactlyInstanceOf( SomeOtherClass.InnerMapper.Target.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java new file mode 100644 index 0000000000..e0fb59d4d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeClass.java @@ -0,0 +1,82 @@ +/* + * 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.test.bugs._611; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Tillmann Gaida + */ +public class SomeClass { + @Mapper + public interface InnerMapper { + InnerMapper INSTANCE = Mappers.getMapper( InnerMapper.class ); + + Target toTarget(Source in); + + class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + } + + public static class SomeInnerClass { + @Mapper + public interface InnerMapper { + InnerMapper INSTANCE = Mappers.getMapper( InnerMapper.class ); + + Target toTarget(Source in); + + class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java new file mode 100644 index 0000000000..2ff2d0d831 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/SomeOtherClass.java @@ -0,0 +1,47 @@ +/* + * 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.test.bugs._611; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Tillmann Gaida + */ +public class SomeOtherClass { + @Mapper + public interface InnerMapper { + InnerMapper INSTANCE = Mappers.getMapper( InnerMapper.class ); + + Target toTarget(Source in); + + class Source { + + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + + public Target(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Issue625Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Issue625Test.java index 3a1b172fbb..81ff528da5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Issue625Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Issue625Test.java @@ -1,37 +1,21 @@ -/** - * Copyright 2012-2015 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.bugs._625; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import org.mapstruct.factory.Mappers; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * @author Andreas Gudian * */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ ObjectFactory.class, Source.class, @@ -40,7 +24,7 @@ }) @IssueKey("625") public class Issue625Test { - @Test + @ProcessorTest public void ignoresFactoryMethods() { Source s = new Source(); s.setProp( "works" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/ObjectFactory.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/ObjectFactory.java index ac5de7a37f..3cdf4a03df 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/ObjectFactory.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/ObjectFactory.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._625; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Source.java index d7804ee0e1..94a811a0f4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._625; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/SourceTargetMapper.java index 15d9e3724b..e11cb05411 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._625; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Target.java index f55b18fcd1..2340f7e09e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_625/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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._625; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Base1.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Base1.java index 247fd1a4de..eccc9fb612 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Base1.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Base1.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._631; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Base2.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Base2.java index 085bb8df38..3e01907520 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Base2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Base2.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.bugs._631; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/ErroneousSourceTargetMapper.java new file mode 100644 index 0000000000..30e4688b8c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/ErroneousSourceTargetMapper.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.ap.test.bugs._631; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + * @param + * @param + */ +@Mapper +public interface ErroneousSourceTargetMapper { + + ErroneousSourceTargetMapper INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper.class ); + + X mapIntegerToBase1(Integer obj); + + Integer mapBase2ToInteger(Y obj); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Issue631Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Issue631Test.java index 668f1c3d1a..57a221c6d3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Issue631Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Issue631Test.java @@ -1,56 +1,40 @@ -/** - * Copyright 2012-2015 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.bugs._631; - import javax.tools.Diagnostic.Kind; -import org.junit.Test; -import org.junit.runner.RunWith; + import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) public class Issue631Test { - @Test + @ProcessorTest @IssueKey("631") @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = SourceTargetMapper.class, + @Diagnostic(type = ErroneousSourceTargetMapper.class, kind = Kind.ERROR, - line = 35, - messageRegExp = "Can't generate mapping method for a generic type variable target."), - @Diagnostic(type = SourceTargetMapper.class, + line = 22, + message = "Can't generate mapping method for a generic type variable target."), + @Diagnostic(type = ErroneousSourceTargetMapper.class, kind = Kind.ERROR, - line = 37, - messageRegExp = "Can't generate mapping method for a generic type variable source.") + line = 24, + message = "Can't generate mapping method for a generic type variable source.") } ) - @WithClasses({SourceTargetMapper.class, Base1.class, Base2.class}) + @WithClasses({ErroneousSourceTargetMapper.class, Base1.class, Base2.class}) public void showsCantMapPropertyError() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/SourceTargetMapper.java deleted file mode 100644 index 6b5bc99a97..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/SourceTargetMapper.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.bugs._631; - -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -/** - * - * @author Sjaak Derksen - * @param - * @param - */ -@Mapper -public interface SourceTargetMapper { - - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - - X mapIntegerToBase1(Integer obj); - - Integer mapBase2ToInteger(Y obj); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Bar.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Bar.java new file mode 100644 index 0000000000..c731414aa6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Bar.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.ap.test.bugs._634; + +public class Bar { + + private long value; + + public Bar() { + } + + public Bar(long value) { + this.value = value; + } + + public long getValue() { + return value; + } + + public void setValue(long value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Foo.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Foo.java new file mode 100644 index 0000000000..10ba1b423e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Foo.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.ap.test.bugs._634; + +public class Foo { + + private String value; + + public Foo(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/GenericContainerTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/GenericContainerTest.java new file mode 100644 index 0000000000..99d609408e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/GenericContainerTest.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.ap.test.bugs._634; + +import java.util.Arrays; +import java.util.List; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Gunnar Morling + */ +@WithClasses({ + Bar.class, + Foo.class, + Source.class, + Target.class, + SourceTargetMapper.class, +}) +public class GenericContainerTest { + + @ProcessorTest + @IssueKey("634") + public void canMapGenericSourceTypeToGenericTargetType() { + List items = Arrays.asList( new Foo( "42" ), new Foo( "84" ) ); + Source source = new Source<>( items ); + + Target target = SourceTargetMapper.INSTANCE.mapSourceToTarget( source ); + + assertThat( target.getContent() ).extracting( "value" ).containsExactly( 42L, 84L ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Source.java new file mode 100644 index 0000000000..e5d3ae101b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/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.ap.test.bugs._634; + +import java.util.List; + +public class Source { + + private final List items; + + public Source(List items) { + this.items = items; + } + + public List getContent() { + return items; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/SourceTargetMapper.java new file mode 100644 index 0000000000..54bb1a2651 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/SourceTargetMapper.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.ap.test.bugs._634; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + Bar mapFooToBar(Foo foo); + + Target mapSourceToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Target.java new file mode 100644 index 0000000000..5c3a1cb892 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_634/Target.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.ap.test.bugs._634; + +import java.util.List; + +public class Target { + + private List content; + + public List getContent() { + return content; + } + + public void setContent(List content) { + this.content = content; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableContainer.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableContainer.java new file mode 100644 index 0000000000..ed2d0df612 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableContainer.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.ap.test.bugs._775; + +/** + * @author Andreas Gudian + */ +public class IterableContainer { + private Iterable values; + + public void setValues(Iterable values) { + this.values = values; + } + + public Iterable getValues() { + return values; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableWithBoundedElementTypeTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableWithBoundedElementTypeTest.java new file mode 100644 index 0000000000..db2f376416 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/IterableWithBoundedElementTypeTest.java @@ -0,0 +1,57 @@ +/* + * 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.test.bugs._775; + +import java.util.Arrays; + +import org.assertj.core.api.IterableAssert; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies: + *

        + *
      • For target properties of type {@link Iterable}, a forged method can be created. + *
      • For target properties of type {@code Iterable}, a custom mapping method that returns + * {@code List} is chosen as property mapping method. + *
      + * + * @author Andreas Gudian + */ +@IssueKey("775") +@WithClasses({ + MapperWithForgedIterableMapping.class, + MapperWithCustomListMapping.class, + ListContainer.class, + IterableContainer.class +}) +public class IterableWithBoundedElementTypeTest { + + @ProcessorTest + public void createsForgedMethodForIterableLowerBoundInteger() { + ListContainer source = new ListContainer(); + + source.setValues( Arrays.asList( "42", "47" ) ); + IterableContainer result = MapperWithForgedIterableMapping.INSTANCE.toContainerWithIterable( source ); + + ( (IterableAssert) assertThat( result.getValues() ) ) + .contains( 42, 47 ); + } + + @ProcessorTest + public void usesListIntegerMethodForIterableLowerBoundInteger() { + ListContainer source = new ListContainer(); + + source.setValues( Arrays.asList( "42", "47" ) ); + IterableContainer result = MapperWithCustomListMapping.INSTANCE.toContainerWithIterable( source ); + + ( (IterableAssert) assertThat( result.getValues() ) ) + .contains( 66, 71 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/ListContainer.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/ListContainer.java new file mode 100644 index 0000000000..77a5303196 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/ListContainer.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.ap.test.bugs._775; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Andreas Gudian + */ +public class ListContainer { + private List values = new ArrayList<>(); + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithCustomListMapping.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithCustomListMapping.java new file mode 100644 index 0000000000..dba7dc2360 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithCustomListMapping.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.test.bugs._775; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper +public abstract class MapperWithCustomListMapping { + public static final MapperWithCustomListMapping INSTANCE = + Mappers.getMapper( MapperWithCustomListMapping.class ); + + public abstract IterableContainer toContainerWithIterable(ListContainer source); + + protected List hexListToIntList(Collection source) { + return source.stream() + .map( string -> Integer.parseInt( string, 16 ) ) + .collect( Collectors.toList() ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithForgedIterableMapping.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithForgedIterableMapping.java new file mode 100644 index 0000000000..c659bf596c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_775/MapperWithForgedIterableMapping.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.ap.test.bugs._775; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper +public abstract class MapperWithForgedIterableMapping { + public static final MapperWithForgedIterableMapping INSTANCE = + Mappers.getMapper( MapperWithForgedIterableMapping.class ); + + public abstract IterableContainer toContainerWithIterable(ListContainer source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/Commit.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/Commit.java new file mode 100644 index 0000000000..431a999c45 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/Commit.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.test.bugs._843; + +import java.util.Date; + +/** + * + * @author Sjaak Derksen + */ +public class Commit { + + private static int callCounter; + + private Date authoredDate; + + public Date getAuthoredDate() { + callCounter++; + return authoredDate; + } + + public void setAuthoredDate(Date authoredDate) { + this.authoredDate = authoredDate; + } + + public static int getCallCounter() { + return callCounter; + } + + public static void resetCallCounter() { + callCounter = 0; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/GitlabTag.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/GitlabTag.java new file mode 100644 index 0000000000..28df16ee08 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/GitlabTag.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.test.bugs._843; + +/** + * + * @author Sjaak Derksen + */ +public class GitlabTag { + + private static int callCounter = 0; + + private Commit commit; + + public Commit getCommit() { + callCounter++; + return commit; + } + + public void setCommit(Commit commit) { + this.commit = commit; + } + + public static int getCallCounter() { + return callCounter; + } + + public static void resetCallCounter() { + callCounter = 0; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/Issue843Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/Issue843Test.java new file mode 100644 index 0000000000..4937f6c3d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/Issue843Test.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._843; + +import java.util.Date; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Sjaak Derksen + */ +@WithClasses({ + Commit.class, + TagInfo.class, + GitlabTag.class, + TagMapper.class +}) +@IssueKey("843") +public class Issue843Test { + + @ProcessorTest + public void testMapperCreation() { + + Commit commit = new Commit(); + commit.setAuthoredDate( new Date() ); + GitlabTag gitlabTag = new GitlabTag(); + gitlabTag.setCommit( commit ); + + Commit.resetCallCounter(); + GitlabTag.resetCallCounter(); + TagMapper.INSTANCE.gitlabTagToTagInfo( gitlabTag ); + + assertThat( Commit.getCallCounter() ).isEqualTo( 1 ); + assertThat( GitlabTag.getCallCounter() ).isEqualTo( 1 ); + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/TagInfo.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/TagInfo.java new file mode 100644 index 0000000000..55af1beb9d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/TagInfo.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.ap.test.bugs._843; + +import java.util.Date; + +/** + * + * @author Sjaak Derksen + */ +public class TagInfo { + + private Date date; + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/TagMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/TagMapper.java new file mode 100644 index 0000000000..334ac52ef0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_843/TagMapper.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.ap.test.bugs._843; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS ) +public interface TagMapper { + + TagMapper INSTANCE = Mappers.getMapper( TagMapper.class ); + + @Mapping(target = "date", source = "gitlabTag.commit.authoredDate") + TagInfo gitlabTagToTagInfo(GitlabTag gitlabTag); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_846/Mapper846.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_846/Mapper846.java new file mode 100644 index 0000000000..5ce79afa03 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_846/Mapper846.java @@ -0,0 +1,65 @@ +/* + * 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.test.bugs._846; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; + +public class Mapper846 { + + interface BInterface { + + String getName(); + + void setName(String s); + } + + static class B implements BInterface { + + String name; + + B() { + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + } + + static class A { + + String aName; + + A() { + } + + public String getaName() { + return aName; + } + + public void setaName(String name) { + this.aName = name; + } + } + + @Mapper + interface MyMapper { + + @Mapping(target = "aName", source = "name") + A convert(BInterface b); + + @InheritInverseConfiguration + void convert(@MappingTarget BInterface target, A source); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_846/UpdateTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_846/UpdateTest.java new file mode 100644 index 0000000000..776f3f70e0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_846/UpdateTest.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.ap.test.bugs._846; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@IssueKey("846") +@WithClasses({ + Mapper846.class +}) +public class UpdateTest { + + @ProcessorTest + public void shouldProduceMapper() { + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Issue849Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Issue849Mapper.java new file mode 100644 index 0000000000..2891971dfd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Issue849Mapper.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.ap.test.bugs._849; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface Issue849Mapper { + + Issue849Mapper INSTANCE = Mappers.getMapper( Issue849Mapper.class ); + + @Mapping(target = "targetList", source = "sourceList") + Target mapSourceToTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Issue849Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Issue849Test.java new file mode 100644 index 0000000000..22ec5bd309 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Issue849Test.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.test.bugs._849; + +import java.util.Arrays; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Andreas Gudian + * + */ +@WithClasses({ Source.class, Target.class, Issue849Mapper.class }) +public class Issue849Test { + + @ProcessorTest + @IssueKey("849") + public void shouldCompileWithAllImportsDeclared() { + + Source source = new Source(); + source.setSourceList( Arrays.asList( "test" ) ); + + Target target = Issue849Mapper.INSTANCE.mapSourceToTarget( source ); + assertThat( target.getTargetList() ).containsExactly( "test" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Source.java new file mode 100644 index 0000000000..f96483aa24 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Source.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.ap.test.bugs._849; + +import java.io.Serializable; +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class Source { + + private List sourceList; + + public List getSourceList() { + return sourceList; + } + + public void setSourceList(List sourceList) { + this.sourceList = sourceList; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Target.java new file mode 100644 index 0000000000..8bd9e9d17b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Target.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.ap.test.bugs._849; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class Target { + + private List targetList; + + public List getTargetList() { + if ( targetList == null ) { + targetList = new ArrayList<>(); + } + return targetList; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderDemoMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderDemoMapper.java new file mode 100644 index 0000000000..f968509d65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderDemoMapper.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.ap.test.bugs._855; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OrderDemoMapper { + OrderDemoMapper INSTANCE = Mappers.getMapper( OrderDemoMapper.class ); + + @Mappings({ + @Mapping(target = "field0", dependsOn = "field2"), + @Mapping(target = "order", ignore = true) + }) + OrderedTarget orderedWithDependsOn(OrderedSource source); + + @Mapping(target = "order", ignore = true) + OrderedTarget orderedWithoutDependsOn(OrderedSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderedSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderedSource.java new file mode 100644 index 0000000000..6cc57147a7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderedSource.java @@ -0,0 +1,57 @@ +/* + * 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.test.bugs._855; + +/** + * @author Markus Heberling + */ +class OrderedSource { + private String field0; + private String field1; + private String field2; + private String field3; + private String field4; + + public String getField0() { + return field0; + } + + public void setField0(String field0) { + this.field0 = field0; + } + + public String getField1() { + return field1; + } + + public void setField1(String field1) { + this.field1 = field1; + } + + public String getField2() { + return field2; + } + + public void setField2(String field2) { + this.field2 = field2; + } + + public String getField3() { + return field3; + } + + public void setField3(String field3) { + this.field3 = field3; + } + + public String getField4() { + return field4; + } + + public void setField4(String field4) { + this.field4 = field4; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderedTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderedTarget.java new file mode 100644 index 0000000000..10e89795b2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderedTarget.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._855; + +import java.util.LinkedList; +import java.util.List; + +/** + * @author Markus Heberling + */ +class OrderedTarget { + private List order = new LinkedList<>(); + + public void setField0(String field0) { + order.add( "field0" ); + } + + public void setField1(String field1) { + order.add( "field1" ); + } + + public void setField2(String field2) { + order.add( "field2" ); + } + + public void setField3(String field3) { + order.add( "field3" ); + } + + public void setField4(String field4) { + order.add( "field4" ); + } + + public List getOrder() { + return order; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderingBug855Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderingBug855Test.java new file mode 100644 index 0000000000..54ee84a89c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_855/OrderingBug855Test.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.ap.test.bugs._855; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Andreas Gudian + * + */ +@WithClasses({ OrderedSource.class, OrderedTarget.class, OrderDemoMapper.class }) +public class OrderingBug855Test { + + @ProcessorTest + @IssueKey("855") + public void shouldApplyCorrectOrderingWithDependsOn() { + OrderedSource source = new OrderedSource(); + + OrderedTarget target = OrderDemoMapper.INSTANCE.orderedWithDependsOn( source ); + + assertThat( target.getOrder() ).containsExactly( "field2", "field0", "field1", "field3", "field4" ); + } + + @ProcessorTest + public void shouldRetainDefaultOrderWithoutDependsOn() { + OrderedSource source = new OrderedSource(); + + OrderedTarget target = OrderDemoMapper.INSTANCE.orderedWithoutDependsOn( source ); + + assertThat( target.getOrder() ).containsExactly( "field0", "field1", "field2", "field3", "field4" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/Issue865Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/Issue865Test.java new file mode 100644 index 0000000000..f5196ec7b7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/Issue865Test.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.test.bugs._865; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Sjaak Derksen + */ +@WithClasses( { + ProjectDto.class, + ProjectEntity.class, + ProjectEntityWithoutSetter.class, + ProjCoreUserEntity.class, + ProjMemberDto.class, + ProjectMapper.class +} ) +public class Issue865Test { + + @ProcessorTest + public void shouldGenerateNpeCheckBeforeCallingAddAllWhenInUpdateMethods() { + + ProjectDto dto = new ProjectDto(); + dto.setName( "myProject" ); + + ProjectEntity entity = new ProjectEntity(); + entity.setCoreUsers( null ); + + ProjectMapper.INSTANCE.mapProjectUpdate( dto, entity ); + + assertThat( entity.getName() ).isEqualTo( "myProject" ); + assertThat( entity.getCoreUsers() ).isNull(); + } + + @ProcessorTest + public void shouldGenerateNpeCheckBeforeCallingAddAllWhenInUpdateMethodsAndTargetWithoutSetter() { + + ProjectDto dto = new ProjectDto(); + dto.setName( "myProject" ); + + ProjectEntityWithoutSetter entity = new ProjectEntityWithoutSetter(); + + ProjectMapper.INSTANCE.mapProjectUpdateWithoutGetter( dto, entity ); + + assertThat( entity.getName() ).isEqualTo( "myProject" ); + assertThat( entity.getCoreUsers() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjCoreUserEntity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjCoreUserEntity.java new file mode 100644 index 0000000000..ee6495f568 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjCoreUserEntity.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.ap.test.bugs._865; + +/** + * + * @author Sjaak Derksen + */ +public class ProjCoreUserEntity { + + private String memberName; + + public String getMemberName() { + return memberName; + } + + public void setMemberName(String memberName) { + this.memberName = memberName; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjMemberDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjMemberDto.java new file mode 100644 index 0000000000..ada4fdc40d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjMemberDto.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.ap.test.bugs._865; + +/** + * + * @author Sjaak Derksen + */ +public class ProjMemberDto { + + private String memberName; + + public String getMemberName() { + return memberName; + } + + public void setMemberName(String memberName) { + this.memberName = memberName; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectDto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectDto.java new file mode 100644 index 0000000000..1bd35bae76 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectDto.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.test.bugs._865; + +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class ProjectDto { + + List projectMembers; + String name; + + public List getProjectMembers() { + return projectMembers; + } + + public void setProjectMembers(List projectMembers) { + this.projectMembers = projectMembers; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectEntity.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectEntity.java new file mode 100644 index 0000000000..dd01abe045 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectEntity.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.test.bugs._865; + +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class ProjectEntity { + + private String name; + private List coreUsers; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getCoreUsers() { + return coreUsers; + } + + public void setCoreUsers(List coreUsers) { + this.coreUsers = coreUsers; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectEntityWithoutSetter.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectEntityWithoutSetter.java new file mode 100644 index 0000000000..8e4ae71129 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectEntityWithoutSetter.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.ap.test.bugs._865; + +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class ProjectEntityWithoutSetter { + + private String name; + private List coreUsers = null; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getCoreUsers() { + return coreUsers; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectMapper.java new file mode 100644 index 0000000000..b23db3d35d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_865/ProjectMapper.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.ap.test.bugs._865; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ProjectMapper { + + ProjectMapper INSTANCE = Mappers.getMapper( ProjectMapper.class ); + + @Mapping(target = "coreUsers", source = "projectMembers") + void mapProjectUpdate(ProjectDto dto, @MappingTarget ProjectEntity entity); + + @Mapping(target = "coreUsers", source = "projectMembers") + void mapProjectUpdateWithoutGetter(ProjectDto dto, @MappingTarget ProjectEntityWithoutSetter entity); + + ProjCoreUserEntity mapUser(ProjMemberDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.java new file mode 100644 index 0000000000..7aef1e9e1f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Config.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.ap.test.bugs._880.spring; + +import org.mapstruct.MapperConfig; +import org.mapstruct.ReportingPolicy; + +/** + * @author Andreas Gudian + */ +@MapperConfig(unmappedTargetPolicy = ReportingPolicy.WARN) +public interface Config { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java new file mode 100644 index 0000000000..5f67b41552 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.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.ap.test.bugs._880.spring; + +import org.mapstruct.Mapper; + +/** + * @author Andreas Gudian + * + */ +@Mapper +public interface DefaultsToProcessorOptionsMapper { + Poodle metamorph(Object essence); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java new file mode 100644 index 0000000000..cd85829147 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.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.ap.test.bugs._880.spring; + +import javax.tools.Diagnostic.Kind; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOptions; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.stereotype.Component; + +/** + * @author Andreas Gudian + */ +@WithClasses({ + UsesConfigFromAnnotationMapper.class, + DefaultsToProcessorOptionsMapper.class, + Poodle.class, + Config.class }) +@ProcessorOptions({ + @ProcessorOption(name = "mapstruct.defaultComponentModel", value = MappingConstants.ComponentModel.SPRING), + @ProcessorOption(name = "mapstruct.unmappedTargetPolicy", value = "ignore") }) +@WithSpring +public class Issue880Test { + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = @Diagnostic(kind = Kind.WARNING, + type = UsesConfigFromAnnotationMapper.class, line = 17, + message = "Unmapped target property: \"core\".")) + public void compilationSucceedsAndAppliesCorrectComponentModel() { + generatedSource.forMapper( UsesConfigFromAnnotationMapper.class ).containsNoImportFor( Component.class ); + + generatedSource.forMapper( DefaultsToProcessorOptionsMapper.class ).containsImportFor( Component.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java new file mode 100644 index 0000000000..71166f6d4c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.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.ap.test.bugs._880.spring; + +/** + * @author Andreas Gudian + * + */ +public class Poodle { + private String core; + + public String getCore() { + return core; + } + + public void setCore(String core) { + this.core = core; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.java new file mode 100644 index 0000000000..bb9a7def9f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/UsesConfigFromAnnotationMapper.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.ap.test.bugs._880.spring; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; + +/** + * @author Andreas Gudian + * + */ +@Mapper(componentModel = MappingConstants.ComponentModel.DEFAULT, config = Config.class) +public interface UsesConfigFromAnnotationMapper { + Poodle metamorph(Object essence); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/BuggyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/BuggyMapper.java new file mode 100644 index 0000000000..fbb9fcd6c9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/BuggyMapper.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.ap.test.bugs._891; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface BuggyMapper { + + BuggyMapper INSTANCE = Mappers.getMapper( BuggyMapper.class ); + + @Mapping(target = "propLong", source = "nested.propInt") + Dest convert(Src src); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Dest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Dest.java new file mode 100644 index 0000000000..f0a8c64936 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Dest.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.ap.test.bugs._891; + +/** + * + * @author Sjaak Derksen + */ +public class Dest { + + public void setPropLong(Long l) { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Issue891Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Issue891Test.java new file mode 100644 index 0000000000..5fae478f2e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Issue891Test.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.ap.test.bugs._891; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Sjaak Derksen + */ +@WithClasses({BuggyMapper.class, Dest.class, Src.class, SrcNested.class}) +public class Issue891Test { + + @ProcessorTest + public void shouldNotThrowNPE() { + + BuggyMapper.INSTANCE.convert( new Src() ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Src.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Src.java new file mode 100644 index 0000000000..f461aba664 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/Src.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.ap.test.bugs._891; + +/** + * + * @author Sjaak Derksen + */ +public class Src { + + public SrcNested getNested() { + return null; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/SrcNested.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/SrcNested.java new file mode 100644 index 0000000000..5557030822 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_891/SrcNested.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.ap.test.bugs._891; + +/** + * + * @author Sjaak Derksen + */ +public class SrcNested { + + public Integer getPropInt() { + return 1; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_892/Issue892Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_892/Issue892Mapper.java new file mode 100644 index 0000000000..84b6d12a15 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_892/Issue892Mapper.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.ap.test.bugs._892; + +import org.mapstruct.Mapper; + +/** + * @author Andreas Gudian + * + */ +@Mapper +public interface Issue892Mapper { + Target toTarget(Source source); + + class Source { + private int type; + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + } + + class Target { + private int type; + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public void setType(String typeInHex) { + this.type = Integer.parseInt( typeInHex, 16 ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_892/Issue892Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_892/Issue892Test.java new file mode 100644 index 0000000000..0064a96338 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_892/Issue892Test.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.ap.test.bugs._892; + +import org.mapstruct.ap.test.bugs._892.Issue892Mapper.Source; +import org.mapstruct.ap.test.bugs._892.Issue892Mapper.Target; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * When having two setter methods with the same name, choose the one with the argument type matching the getter method. + * + * @author Andreas Gudian + */ +@WithClasses({ Issue892Mapper.class }) +public class Issue892Test { + @ProcessorTest + public void compiles() { + Source src = new Source(); + src.setType( 42 ); + + Target target = Mappers.getMapper( Issue892Mapper.class ).toTarget( src ); + assertThat( target.getType() ).isEqualTo( 42 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_895/Issue895Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_895/Issue895Test.java new file mode 100644 index 0000000000..6250e139f2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_895/Issue895Test.java @@ -0,0 +1,76 @@ +/* + * 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.test.bugs._895; + +import org.mapstruct.ap.test.bugs._895.MultiArrayMapper.WithArrayOfByteArray; +import org.mapstruct.ap.test.bugs._895.MultiArrayMapper.WithArrayOfGenericArray; +import org.mapstruct.ap.test.bugs._895.MultiArrayMapper.WithListOfByteArray; +import org.mapstruct.ap.test.bugs._895.MultiArrayMapper.WithListOfGenericArray; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that forged iterable mapping methods for multidimensional arrays are generated properly. + * + * @author Andreas Gudian + */ +@IssueKey("895") +@WithClasses(MultiArrayMapper.class) +public class Issue895Test { + @ProcessorTest + public void properlyMapsMultiDimensionalArrays() { + WithArrayOfByteArray arrayOfByteArray = new WithArrayOfByteArray(); + arrayOfByteArray.setBytes( new byte[][] { new byte[] { 0, 1 }, new byte[] { 1, 2 } } ); + + WithListOfByteArray listOfByteArray = Mappers.getMapper( MultiArrayMapper.class ).convert( arrayOfByteArray ); + assertThat( listOfByteArray.getBytes() ).containsExactly( new byte[] { 0, 1 }, new byte[] { 1, 2 } ); + + arrayOfByteArray = Mappers.getMapper( MultiArrayMapper.class ).convert( listOfByteArray ); + assertThat( arrayOfByteArray.getBytes() ).isDeepEqualTo( new byte[][] { { 0, 1 }, { 1, 2 } } ); + } + + @ProcessorTest + public void properlyMapsGenericMultiDimensionalArrays() { + WithArrayOfGenericArray arrayOfStringArray = new MultiArrayMapper.WithArrayOfGenericArray<>(); + arrayOfStringArray.setData( new String[][] { new String[] { "a", "b" }, new String[] { "b", "c" } } ); + + MultiArrayMapper mapper = Mappers.getMapper( MultiArrayMapper.class ); + WithListOfGenericArray listOfStringArray = mapper.convertGeneric( arrayOfStringArray ); + assertThat( listOfStringArray.getData() ) + .containsExactly( new String[] { "a", "b" }, new String[] { "b", "c" } ); + + arrayOfStringArray = mapper.convertGeneric( listOfStringArray ); + assertThat( arrayOfStringArray.getData() ) + .isDeepEqualTo( new String[][] { new String[] { "a", "b" }, new String[] { "b", "c" } } ); + } + + @ProcessorTest + public void properlyReturnDefaultForMultiArray() { + assertThat( Mappers.getMapper( MultiArrayMapper.class ).copyMultiArray( null ) ).isEmpty(); + } + + @ProcessorTest + @SuppressWarnings("unchecked") + public void properlyMapsGenericMultiArray() { + WithArrayOfGenericArray arrayOfGenericArray = new WithArrayOfGenericArray<>(); + arrayOfGenericArray.setData( new String[][] { new String[] { "a", "b" }, new String[] { "b", "c" } } ); + WithArrayOfGenericArray[][] multiArrayOfGenericArray = + new WithArrayOfGenericArray[][]{ new WithArrayOfGenericArray[]{ arrayOfGenericArray } }; + WithArrayOfGenericArray[][] result = + Mappers.getMapper( MultiArrayMapper.class ).copyGenericMultiArray( multiArrayOfGenericArray ); + assertThat( result ).isNotSameAs( multiArrayOfGenericArray ); + assertThat( result ).isDeepEqualTo( multiArrayOfGenericArray ); + } + + @ProcessorTest + public void properlyReturnDefaultForGenericMultiArray() { + assertThat( Mappers.getMapper( MultiArrayMapper.class ).copyGenericMultiArray( null ) ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_895/MultiArrayMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_895/MultiArrayMapper.java new file mode 100644 index 0000000000..13a67125b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_895/MultiArrayMapper.java @@ -0,0 +1,80 @@ +/* + * 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.test.bugs._895; + +import java.util.List; + +import org.mapstruct.IterableMapping; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface MultiArrayMapper { + WithListOfByteArray convert(WithArrayOfByteArray b); + + WithArrayOfByteArray convert(WithListOfByteArray b); + + class WithArrayOfByteArray { + private byte[][] bytes; + + public byte[][] getBytes() { + return bytes; + } + + public void setBytes(byte[][] bytes) { + this.bytes = bytes; + } + } + + @IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) + int[][] copyMultiArray(int[][] multiArray); + + WithListOfGenericArray convertGeneric(WithArrayOfGenericArray b); + + WithArrayOfGenericArray convertGeneric(WithListOfGenericArray b); + + @IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) + WithArrayOfGenericArray[][] copyGenericMultiArray(WithArrayOfGenericArray[][] multiArray); + + class WithListOfByteArray { + private List bytes; + + public List getBytes() { + return bytes; + } + + public void setBytes(List bytes) { + this.bytes = bytes; + } + } + + class WithArrayOfGenericArray { + private T [][] data; + + public T[][] getData() { + return data; + } + + public void setData(T[][] data) { + this.data = data; + } + } + + class WithListOfGenericArray { + private List data; + + public List getData() { + return data; + } + + public void setData(List data) { + this.data = data; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_909/Issue909Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_909/Issue909Test.java new file mode 100644 index 0000000000..20710488f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_909/Issue909Test.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.ap.test.bugs._909; + +import org.mapstruct.ap.test.bugs._909.ValuesMapper.ValuesHolder; +import org.mapstruct.ap.test.bugs._909.ValuesMapper.ValuesHolderDto; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that forged iterable mapping methods for multi-dimensional arrays are generated properly. + * + * @author Andreas Gudian + */ +@WithClasses(ValuesMapper.class) +public class Issue909Test { + @ProcessorTest + public void properlyCreatesMapperWithValuesAsParameterName() { + ValuesHolder valuesHolder = new ValuesHolder(); + valuesHolder.setValues( "values" ); + + ValuesHolderDto dto = Mappers.getMapper( ValuesMapper.class ).convert( valuesHolder ); + assertThat( dto.getValues() ).isEqualTo( "values" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_909/ValuesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_909/ValuesMapper.java new file mode 100644 index 0000000000..fb4cede674 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_909/ValuesMapper.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.ap.test.bugs._909; + +import org.mapstruct.Mapper; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface ValuesMapper { + ValuesHolderDto convert(ValuesHolder values); + + ValuesHolderDto convert(ValuesHolder values, int test); + + ValuesHolderDto convertOther(ValuesHolder valuesHolder, int values); + + class ValuesHolder { + private String values; + + public String getValues() { + return values; + } + + public void setValues(String values) { + this.values = values; + } + } + + class ValuesHolderDto { + private String values; + + public String getValues() { + return values; + } + + public void setValues(String values) { + this.values = values; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Domain.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Domain.java new file mode 100644 index 0000000000..500adf3066 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Domain.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.ap.test.bugs._913; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * + * @author Sjaak Derksen + */ +public class Domain { + static final Set DEFAULT_STRINGS = new HashSet<>(); + static final Set DEFAULT_LONGS = new HashSet<>(); + + private Set strings = DEFAULT_STRINGS; + private Set longs = DEFAULT_LONGS; + private Set stringsInitialized; + private Set longsInitialized; + private List stringsWithDefault; + + public Set getStrings() { + return strings; + } + + public void setStrings(Set strings) { + this.strings = strings; + } + + public Set getLongs() { + return longs; + } + + public void setLongs(Set longs) { + this.longs = longs; + } + + public Set getStringsInitialized() { + return stringsInitialized; + } + + public void setStringsInitialized(Set stringsInitialized) { + this.stringsInitialized = stringsInitialized; + } + + public Set getLongsInitialized() { + return longsInitialized; + } + + public void setLongsInitialized(Set longsInitialized) { + this.longsInitialized = longsInitialized; + } + + public List getStringsWithDefault() { + return stringsWithDefault; + } + + public void setStringsWithDefault(List stringsWithDefault) { + this.stringsWithDefault = stringsWithDefault; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapper.java new file mode 100644 index 0000000000..11be515838 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNcvsAlwaysMapper.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.ap.test.bugs._913; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.ObjectFactory; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper(uses = Helper.class, nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS ) +public interface DomainDtoWithNcvsAlwaysMapper { + + DomainDtoWithNcvsAlwaysMapper INSTANCE = Mappers.getMapper( DomainDtoWithNcvsAlwaysMapper.class ); + + @BeanMapping( qualifiedByName = "DomainObjectFactory" ) + @Mappings({ + @Mapping(target = "strings", source = "strings"), + @Mapping(target = "longs", source = "strings"), + @Mapping(target = "stringsInitialized", source = "stringsInitialized"), + @Mapping(target = "longsInitialized", source = "stringsInitialized"), + @Mapping(target = "stringsWithDefault", source = "stringsWithDefault", defaultValue = "3") + }) + Domain create(DtoWithPresenceCheck source); + + @InheritConfiguration( name = "create" ) + void update(DtoWithPresenceCheck source, @MappingTarget Domain target); + + @InheritConfiguration( name = "create" ) + Domain updateWithReturn(DtoWithPresenceCheck source, @MappingTarget Domain target); + + @ObjectFactory + @Named( "DomainObjectFactory" ) + default Domain createNullDomain() { + Domain domain = new Domain(); + domain.setLongs( null ); + domain.setStrings( null ); + return domain; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapper.java new file mode 100644 index 0000000000..7cd3cfedef --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsDefaultMapper.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.ap.test.bugs._913; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT, + uses = Helper.class ) +public interface DomainDtoWithNvmsDefaultMapper { + + DomainDtoWithNvmsDefaultMapper INSTANCE = Mappers.getMapper( DomainDtoWithNvmsDefaultMapper.class ); + + @Mappings({ + @Mapping(target = "strings", source = "strings"), + @Mapping(target = "longs", source = "strings"), + @Mapping(target = "stringsInitialized", source = "stringsInitialized"), + @Mapping(target = "longsInitialized", source = "stringsInitialized"), + @Mapping(target = "stringsWithDefault", source = "stringsWithDefault", defaultValue = "3") + }) + Domain create(Dto source); + + @InheritConfiguration( name = "create" ) + void update(Dto source, @MappingTarget Domain target); + + @InheritConfiguration( name = "create" ) + Domain updateWithReturn(Dto source, @MappingTarget Domain target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapper.java new file mode 100644 index 0000000000..d288b666f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithNvmsNullMapper.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.ap.test.bugs._913; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.ObjectFactory; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper(uses = Helper.class) +// this is the default nvms, so no need to define +public interface DomainDtoWithNvmsNullMapper { + + DomainDtoWithNvmsNullMapper INSTANCE = Mappers.getMapper( DomainDtoWithNvmsNullMapper.class ); + + @BeanMapping( qualifiedByName = "DomainObjectFactory" ) + @Mappings({ + @Mapping(target = "strings", source = "strings"), + @Mapping(target = "longs", source = "strings"), + @Mapping(target = "stringsInitialized", source = "stringsInitialized"), + @Mapping(target = "longsInitialized", source = "stringsInitialized"), + @Mapping(target = "stringsWithDefault", source = "stringsWithDefault", defaultValue = "3") + }) + Domain create(Dto source); + + @InheritConfiguration( name = "create" ) + void update(Dto source, @MappingTarget Domain target); + + @InheritConfiguration( name = "create" ) + Domain updateWithReturn(Dto source, @MappingTarget Domain target); + + @ObjectFactory + @Named( "DomainObjectFactory" ) + default Domain createNullDomain() { + Domain domain = new Domain(); + domain.setLongs( null ); + domain.setStrings( null ); + return domain; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapper.java new file mode 100644 index 0000000000..83b8539a41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainDtoWithPresenceCheckMapper.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.ap.test.bugs._913; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.Named; +import org.mapstruct.ObjectFactory; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper(uses = Helper.class) +// this is the default nvms, so no need to define +public interface DomainDtoWithPresenceCheckMapper { + + DomainDtoWithPresenceCheckMapper INSTANCE = Mappers.getMapper( DomainDtoWithPresenceCheckMapper.class ); + + @BeanMapping( qualifiedByName = "DomainObjectFactory" ) + @Mappings({ + @Mapping(target = "strings", source = "strings"), + @Mapping(target = "longs", source = "strings"), + @Mapping(target = "stringsInitialized", source = "stringsInitialized"), + @Mapping(target = "longsInitialized", source = "stringsInitialized"), + @Mapping(target = "stringsWithDefault", source = "stringsWithDefault", defaultValue = "3") + }) + Domain create(DtoWithPresenceCheck source); + + @InheritConfiguration( name = "create" ) + void update(DtoWithPresenceCheck source, @MappingTarget Domain target); + + @InheritConfiguration( name = "create" ) + Domain updateWithReturn(DtoWithPresenceCheck source, @MappingTarget Domain target); + + @ObjectFactory + @Named( "DomainObjectFactory" ) + default Domain createNullDomain() { + Domain domain = new Domain(); + domain.setLongs( null ); + domain.setStrings( null ); + return domain; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetter.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetter.java new file mode 100644 index 0000000000..a808d78737 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetter.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.ap.test.bugs._913; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * + * @author Sjaak Derksen + */ +public class DomainWithoutSetter { + + private final Set strings = new HashSet<>(); + private final Set longs = new HashSet<>(); + private final Set stringsInitialized = new HashSet<>(); + private final Set longsInitialized = new HashSet<>(); + private final List stringsWithDefault = new ArrayList<>(); + + public Set getStrings() { + return strings; + } + + public Set getLongs() { + return longs; + } + + public Set getStringsInitialized() { + return stringsInitialized; + } + + public Set getLongsInitialized() { + return longsInitialized; + } + + public List getStringsWithDefault() { + return stringsWithDefault; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithNvmsDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithNvmsDefaultMapper.java new file mode 100644 index 0000000000..74564f7859 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithNvmsDefaultMapper.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._913; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, uses = Helper.class ) +public interface DomainWithoutSetterDtoWithNvmsDefaultMapper { + + DomainWithoutSetterDtoWithNvmsDefaultMapper INSTANCE = + Mappers.getMapper( DomainWithoutSetterDtoWithNvmsDefaultMapper.class ); + + @Mappings({ + @Mapping(target = "strings", source = "strings"), + @Mapping(target = "longs", source = "strings"), + @Mapping(target = "stringsInitialized", source = "stringsInitialized"), + @Mapping(target = "longsInitialized", source = "stringsInitialized"), + @Mapping(target = "stringsWithDefault", source = "stringsWithDefault", defaultValue = "3") + }) + DomainWithoutSetter create(Dto source); + + @InheritConfiguration( name = "create" ) + void update(Dto source, @MappingTarget DomainWithoutSetter target); + + @InheritConfiguration( name = "create" ) + DomainWithoutSetter updateWithReturn(Dto source, @MappingTarget DomainWithoutSetter target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithNvmsNullMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithNvmsNullMapper.java new file mode 100644 index 0000000000..90916555ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithNvmsNullMapper.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._913; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper(uses = Helper.class) +// this is the default nvms, so no need to define +public interface DomainWithoutSetterDtoWithNvmsNullMapper { + + DomainWithoutSetterDtoWithNvmsNullMapper INSTANCE + = Mappers.getMapper( DomainWithoutSetterDtoWithNvmsNullMapper.class ); + + @Mappings({ + @Mapping(target = "strings", source = "strings"), + @Mapping(target = "longs", source = "strings"), + @Mapping(target = "stringsInitialized", source = "stringsInitialized"), + @Mapping(target = "longsInitialized", source = "stringsInitialized"), + @Mapping(target = "stringsWithDefault", source = "stringsWithDefault", defaultValue = "3") + }) + DomainWithoutSetter create(Dto source); + + @InheritConfiguration( name = "create" ) + void update(Dto source, @MappingTarget DomainWithoutSetter target); + + @InheritConfiguration( name = "create" ) + DomainWithoutSetter updateWithReturn(Dto source, @MappingTarget DomainWithoutSetter target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithPresenceCheckMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithPresenceCheckMapper.java new file mode 100644 index 0000000000..b3ed59852c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DomainWithoutSetterDtoWithPresenceCheckMapper.java @@ -0,0 +1,40 @@ +/* + * 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.test.bugs._913; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper(uses = Helper.class) +// this is the default nvms, so no need to define +public interface DomainWithoutSetterDtoWithPresenceCheckMapper { + + DomainWithoutSetterDtoWithPresenceCheckMapper INSTANCE + = Mappers.getMapper( DomainWithoutSetterDtoWithPresenceCheckMapper.class ); + + @Mappings({ + @Mapping(target = "strings", source = "strings"), + @Mapping(target = "longs", source = "strings"), + @Mapping(target = "stringsInitialized", source = "stringsInitialized"), + @Mapping(target = "longsInitialized", source = "stringsInitialized"), + @Mapping(target = "stringsWithDefault", source = "stringsWithDefault", defaultValue = "3") + }) + DomainWithoutSetter create(DtoWithPresenceCheck source); + + @InheritConfiguration( name = "create" ) + void update(DtoWithPresenceCheck source, @MappingTarget DomainWithoutSetter target); + + @InheritConfiguration( name = "create" ) + DomainWithoutSetter updateWithReturn(DtoWithPresenceCheck source, @MappingTarget DomainWithoutSetter target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Dto.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Dto.java new file mode 100644 index 0000000000..507180bc07 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Dto.java @@ -0,0 +1,46 @@ +/* + * 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.test.bugs._913; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class Dto { + + private List strings; + private List stringsInitialized = new ArrayList<>( Arrays.asList( "5" ) ); + private List stringsWithDefault; + + public List getStrings() { + return strings; + } + + public void setStrings(List strings) { + this.strings = strings; + } + + public List getStringsInitialized() { + return stringsInitialized; + } + + public void setStringsInitialized(List stringsInitialized) { + this.stringsInitialized = stringsInitialized; + } + + public List getStringsWithDefault() { + return stringsWithDefault; + } + + public void setStringsWithDefault(List stringsWithDefault) { + this.stringsWithDefault = stringsWithDefault; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DtoWithPresenceCheck.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DtoWithPresenceCheck.java new file mode 100644 index 0000000000..27c262da12 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/DtoWithPresenceCheck.java @@ -0,0 +1,58 @@ +/* + * 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.test.bugs._913; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class DtoWithPresenceCheck { + + private List strings; + private List stringsInitialized = new ArrayList<>( Arrays.asList( "5" ) ); + private List stringsWithDefault; + + public boolean hasStrings() { + return false; + } + + public List getStrings() { + return strings; + } + + public void setStrings(List strings) { + this.strings = strings; + } + + public boolean hasStringsInitialized() { + return true; + } + + public List getStringsInitialized() { + return stringsInitialized; + } + + public void setStringsInitialized(List stringsInitialized) { + this.stringsInitialized = stringsInitialized; + } + + public boolean hasStringsWithDefault() { + return false; + } + + public List getStringsWithDefault() { + return stringsWithDefault; + } + + public void setStringsWithDefault(List stringsWithDefault) { + this.stringsWithDefault = stringsWithDefault; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Helper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Helper.java new file mode 100644 index 0000000000..91c8db655f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Helper.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.ap.test.bugs._913; + +import java.util.Arrays; +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class Helper { + + public List toList(String in) { + return Arrays.asList( in.split( "," ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Issue913GetterMapperForCollectionsTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Issue913GetterMapperForCollectionsTest.java new file mode 100644 index 0000000000..4e10a4895f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Issue913GetterMapperForCollectionsTest.java @@ -0,0 +1,228 @@ +/* + * 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.test.bugs._913; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * All these test cases test the possible combinations in the GetterMapperForCollections. + * + * The target object is assumed to have getter and setter access. + * + * @author Sjaak Derksen + */ +@WithClasses({ + DomainWithoutSetter.class, + Dto.class, + DtoWithPresenceCheck.class, + DomainWithoutSetterDtoWithNvmsNullMapper.class, + DomainWithoutSetterDtoWithNvmsDefaultMapper.class, + DomainWithoutSetterDtoWithPresenceCheckMapper.class, + Helper.class}) +@IssueKey( "913" ) +public class Issue913GetterMapperForCollectionsTest { + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return null in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + */ + @ProcessorTest + public void shouldReturnNullForNvmsReturnNullForCreate() { + + Dto dto = new Dto(); + DomainWithoutSetter domain = DomainWithoutSetterDtoWithNvmsNullMapper.INSTANCE.create( dto ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isEmpty(); + assertThat( domain.getLongs() ).isEmpty(); + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return null in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + */ + @ProcessorTest + public void shouldReturnNullForNvmsReturnNullForUpdate() { + + Dto dto = new Dto(); + DomainWithoutSetter domain = new DomainWithoutSetter(); + DomainWithoutSetterDtoWithNvmsNullMapper.INSTANCE.update( dto, domain ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isEmpty(); + assertThat( domain.getLongs() ).isEmpty(); + + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return null in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + */ + @ProcessorTest + public void shouldReturnNullForNvmsReturnNullForUpdateWithReturn() { + + Dto dto = new Dto(); + DomainWithoutSetter domain1 = new DomainWithoutSetter(); + DomainWithoutSetter domain2 = + DomainWithoutSetterDtoWithNvmsNullMapper.INSTANCE.updateWithReturn( dto, domain1 ); + + doControlAsserts( domain1, domain2 ); + assertThat( domain1.getStrings() ).isEmpty(); + assertThat( domain1.getLongs() ).isEmpty(); + assertThat( domain2.getStrings() ).isEmpty(); + assertThat( domain2.getLongs() ).isEmpty(); + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return default in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + * + * However, for plain mappings (strings to strings) the result will be null. + */ + @ProcessorTest + public void shouldReturnDefaultForNvmsReturnDefaultForCreate() { + + Dto dto = new Dto(); + DomainWithoutSetter domain = DomainWithoutSetterDtoWithNvmsDefaultMapper.INSTANCE.create( dto ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isEmpty(); + assertThat( domain.getLongs() ).isEmpty(); + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the conversion from + * string to long that return default in the entire mapper, so also for the forged mapper. Note the default NVMS is + * RETURN_NULL. + * + * However, for plain mappings (strings to strings) the result will be null. + */ + @ProcessorTest + public void shouldReturnDefaultForNvmsReturnDefaultForUpdate() { + + Dto dto = new Dto(); + DomainWithoutSetter domain = new DomainWithoutSetter(); + DomainWithoutSetterDtoWithNvmsDefaultMapper.INSTANCE.update( dto, domain ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isEmpty(); + assertThat( domain.getLongs() ).isEmpty(); + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return default in the entire mapper, so also for the forged + * mapper. Note the default NVMS is + * RETURN_NULL. + * + * However, for plain mappings (strings to strings) the result will be null. + * + */ + @ProcessorTest + public void shouldReturnDefaultForNvmsReturnDefaultForUpdateWithReturn() { + + Dto dto = new Dto(); + DomainWithoutSetter domain1 = new DomainWithoutSetter(); + DomainWithoutSetter domain2 = + DomainWithoutSetterDtoWithNvmsDefaultMapper.INSTANCE.updateWithReturn( dto, domain1 ); + + doControlAsserts( domain1, domain2 ); + assertThat( domain1.getLongs() ).isEqualTo( domain2.getLongs() ); + assertThat( domain1.getStrings() ).isEmpty(); + assertThat( domain1.getLongs() ).isEmpty(); + assertThat( domain2.getStrings() ).isEmpty(); + assertThat( domain2.getLongs() ).isEmpty(); + } + + /** + * Test create method ICW presence checker + * + */ + @ProcessorTest + public void shouldReturnNullForCreateWithPresenceChecker() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + DomainWithoutSetter domain = DomainWithoutSetterDtoWithPresenceCheckMapper.INSTANCE.create( dto ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isEmpty(); + assertThat( domain.getLongs() ).isEmpty(); + } + + /** + * Test update method ICW presence checker + * + */ + @ProcessorTest + public void shouldReturnNullForUpdateWithPresenceChecker() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + DomainWithoutSetter domain = new DomainWithoutSetter(); + DomainWithoutSetterDtoWithPresenceCheckMapper.INSTANCE.update( dto, domain ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isEmpty(); + assertThat( domain.getLongs() ).isEmpty(); + } + + /** + * Test update with return method ICW presence checker + * + */ + @ProcessorTest + public void shouldReturnNullForUpdateWithReturnWithPresenceChecker() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + DomainWithoutSetter domain1 = new DomainWithoutSetter(); + DomainWithoutSetter domain2 = + DomainWithoutSetterDtoWithPresenceCheckMapper.INSTANCE.updateWithReturn( dto, domain1 ); + + doControlAsserts( domain1, domain2 ); + assertThat( domain1.getLongs() ).isEqualTo( domain2.getLongs() ); + assertThat( domain1.getStrings() ).isEmpty(); + assertThat( domain1.getLongs() ).isEmpty(); + assertThat( domain2.getStrings() ).isEmpty(); + assertThat( domain2.getLongs() ).isEmpty(); + } + + /** + * These assert check if non-null and default mapping is working as expected. + * + * @param domain + */ + private void doControlAsserts( DomainWithoutSetter domain ) { + assertThat( domain.getStringsInitialized() ).containsOnly( "5" ); + assertThat( domain.getLongsInitialized() ).containsOnly( 5L ); + assertThat( domain.getStringsWithDefault() ).containsOnly( "3" ); + } + + /** + * These assert check if non-null and default mapping is working as expected. + * + * @param domain + */ + private void doControlAsserts( DomainWithoutSetter domain1, DomainWithoutSetter domain2) { + assertThat( domain1 ).isEqualTo( domain2 ); + assertThat( domain1.getStringsInitialized() ).containsOnly( "5" ); + assertThat( domain1.getLongsInitialized() ).containsOnly( 5L ); + assertThat( domain1.getStringsWithDefault() ).containsOnly( "3" ); + assertThat( domain2.getStringsInitialized() ).containsOnly( "5" ); + assertThat( domain2.getLongsInitialized() ).containsOnly( 5L ); + assertThat( domain2.getStringsWithDefault() ).containsOnly( "3" ); + assertThat( domain1.getStringsInitialized() ).isEqualTo( domain2.getStringsInitialized() ); + assertThat( domain1.getLongsInitialized() ).isEqualTo( domain2.getLongsInitialized() ); + assertThat( domain1.getStringsWithDefault() ).isEqualTo( domain2.getStringsWithDefault() ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Issue913SetterMapperForCollectionsTest.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Issue913SetterMapperForCollectionsTest.java new file mode 100644 index 0000000000..a549011446 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_913/Issue913SetterMapperForCollectionsTest.java @@ -0,0 +1,363 @@ +/* + * 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.test.bugs._913; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * All these test cases test the possible combinations in the SetterMapperForCollections. + * + * The target object is assumed to have getter and setter access. + * + * @author Sjaak Derksen + */ +@WithClasses({ + Domain.class, + Dto.class, + DtoWithPresenceCheck.class, + DomainDtoWithNvmsNullMapper.class, + DomainDtoWithNvmsDefaultMapper.class, + DomainDtoWithPresenceCheckMapper.class, + DomainDtoWithNcvsAlwaysMapper.class, + Helper.class}) +@IssueKey( "913" ) +public class Issue913SetterMapperForCollectionsTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + DomainDtoWithNvmsNullMapper.class, + DomainDtoWithNvmsDefaultMapper.class, + DomainDtoWithPresenceCheckMapper.class, + DomainDtoWithNcvsAlwaysMapper.class + ); + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return null in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + * + * The generated code should not use a local variable for setting {@code longs}, but it should use a local + * variable for setting {@code strings} as a direct assignment and the copy constructor is used, in case the + * assignment is {@code null} then {@code null} should be set for {@code string} + */ + @ProcessorTest + public void shouldReturnNullForNvmsReturnNullForCreate() { + + Dto dto = new Dto(); + Domain domain = DomainDtoWithNvmsNullMapper.INSTANCE.create( dto ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isNull(); + assertThat( domain.getLongs() ).isNull(); + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return null in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + */ + @ProcessorTest + public void shouldReturnNullForNvmsReturnNullForUpdate() { + + Dto dto = new Dto(); + Domain domain = new Domain(); + domain.setLongs( new HashSet<>() ); + domain.setStrings( new HashSet<>() ); + DomainDtoWithNvmsNullMapper.INSTANCE.update( dto, domain ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isNull(); + assertThat( domain.getLongs() ).isNull(); + + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return null in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + * + * target (stringsInitialized is Not Null) and source (stringInitialized is Null) target should + * be explicitely set to null + */ + @ProcessorTest + public void shouldReturnNullForNvmsReturnNullForUpdateWithNonNullTargetAndNullSource() { + + Dto dto = new Dto(); + dto.setStringsInitialized( null ); + Domain domain = new Domain(); + domain.setLongs( new HashSet<>() ); + domain.setStrings( new HashSet<>() ); + DomainDtoWithNvmsNullMapper.INSTANCE.update( dto, domain ); + + assertThat( domain.getStringsInitialized() ).isNull(); + assertThat( domain.getLongsInitialized() ).isNull(); + assertThat( domain.getStringsWithDefault() ).containsOnly( "3" ); + assertThat( domain.getStrings() ).isNull(); + assertThat( domain.getLongs() ).isNull(); + + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return null in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + */ + @ProcessorTest + public void shouldReturnNullForNvmsReturnNullForUpdateWithReturn() { + + Dto dto = new Dto(); + Domain domain1 = new Domain(); + domain1.setLongs( new HashSet<>() ); + domain1.setStrings( new HashSet<>() ); + Domain domain2 = DomainDtoWithNvmsNullMapper.INSTANCE.updateWithReturn( dto, domain1 ); + + doControlAsserts( domain1, domain2 ); + assertThat( domain1.getStrings() ).isNull(); + assertThat( domain1.getLongs() ).isNull(); + assertThat( domain2.getStrings() ).isNull(); + assertThat( domain2.getLongs() ).isNull(); + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return default in the entire mapper, so also for the forged + * mapper. Note the default NVMS is RETURN_NULL. + * + * However, for plain mappings (strings to strings) the result will also be an empty collection. + */ + @ProcessorTest + public void shouldReturnDefaultForNvmsReturnDefaultForCreate() { + + Dto dto = new Dto(); + Domain domain = DomainDtoWithNvmsDefaultMapper.INSTANCE.create( dto ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isEmpty(); + assertThat( domain.getLongs() ).isEmpty(); + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the conversion from + * string to long that return default in the entire mapper, so also for the forged mapper. Note the default NVMS is + * RETURN_NULL. + * + * However, for plain mappings (strings to strings) the result will also be an empty collection. + */ + @ProcessorTest + public void shouldReturnDefaultForNvmsReturnDefaultForUpdate() { + + Dto dto = new Dto(); + Domain domain = new Domain(); + Set longIn = new HashSet<>(); + longIn.add( 10L ); + domain.setLongs( longIn ); + domain.setStrings( new HashSet<>() ); + DomainDtoWithNvmsDefaultMapper.INSTANCE.update( dto, domain ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isEmpty(); + assertThat( domain.getLongs() ).isEmpty(); + assertThat( domain.getLongs() ).isSameAs( longIn ); // make sure add all is used. + } + + /** + * The null value mapping strategy on type level (Mapper) should generate forged methods for the + * conversion from string to long that return default in the entire mapper, so also for the forged + * mapper. Note the default NVMS is + * RETURN_NULL. + * + * However, for plain mappings (strings to strings) the result will also be an empty collection. + * + */ + @ProcessorTest + public void shouldReturnDefaultForNvmsReturnDefaultForUpdateWithReturn() { + + Dto dto = new Dto(); + Domain domain1 = new Domain(); + Set longIn = new HashSet<>(); + longIn.add( 10L ); + domain1.setLongs( longIn ); + domain1.setStrings( new HashSet<>() ); + domain1.getStrings().add( "30" ); + Domain domain2 = DomainDtoWithNvmsDefaultMapper.INSTANCE.updateWithReturn( dto, domain1 ); + + assertThat( domain2 ).isSameAs( domain1 ); + doControlAsserts( domain1, domain2 ); + assertThat( domain1.getLongs() ).isEqualTo( domain2.getLongs() ); + assertThat( domain1.getStrings() ).isEmpty(); + assertThat( domain1.getLongs() ).isEmpty(); + assertThat( domain2.getStrings() ).isEmpty(); + assertThat( domain2.getLongs() ).isEmpty(); + assertThat( domain1.getLongs() ).isSameAs( longIn ); // make sure that add all is used + assertThat( domain2.getLongs() ).isSameAs( longIn ); // make sure that add all is used + } + + /** + * Test create method ICW presence checker. The presence checker is responsible for the null check. + * + */ + @ProcessorTest + public void shouldReturnNullForCreateWithPresenceChecker() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + Domain domain = DomainDtoWithPresenceCheckMapper.INSTANCE.create( dto ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isNull(); + assertThat( domain.getLongs() ).isNull(); + } + + /** + * Test update method ICW presence checker + * + * Similar as in regular mappings, the target property should be left as-is. + * + */ + @IssueKey( "#954") + @ProcessorTest + public void shouldReturnNullForUpdateWithPresenceChecker() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + Domain domain = new Domain(); + domain.setLongs( new HashSet<>() ); + domain.getLongs().add( 10L ); + domain.setStrings( new HashSet<>() ); + domain.getStrings().add( "30" ); + DomainDtoWithPresenceCheckMapper.INSTANCE.update( dto, domain ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).containsExactly( "30" ); + assertThat( domain.getLongs() ).containsExactly( 10L ); + } + + /** + * Test update with return method ICW presence checker + * + * Similar as in regular mappings, the target property should be left as-is. + * + */ + @IssueKey( "#954") + @ProcessorTest + public void shouldReturnNullForUpdateWithReturnWithPresenceChecker() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + Domain domain1 = new Domain(); + domain1.setLongs( new HashSet<>() ); + domain1.getLongs().add( 10L ); + domain1.setStrings( new HashSet<>() ); + domain1.getStrings().add( "30" ); + Domain domain2 = DomainDtoWithPresenceCheckMapper.INSTANCE.updateWithReturn( dto, domain1 ); + + assertThat( domain2 ).isSameAs( domain1 ); + doControlAsserts( domain1, domain2 ); + assertThat( domain1.getLongs() ).isEqualTo( domain2.getLongs() ); + assertThat( domain1.getStrings() ).containsExactly( "30" ); + assertThat( domain1.getLongs() ).containsExactly( 10L ); + assertThat( domain2.getStrings() ).containsExactly( "30" ); + assertThat( domain2.getLongs() ).containsExactly( 10L ); + } + + /** + * Test create method ICW NullValueCheckStrategy.ALWAYS. + * + */ + @IssueKey( "#954") + @ProcessorTest + public void shouldReturnNullForCreateWithNcvsAlways() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + Domain domain = DomainDtoWithNcvsAlwaysMapper.INSTANCE.create( dto ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).isNull(); + assertThat( domain.getLongs() ).isNull(); + } + + /** + * Test update method ICW presence checker + * + * Similar as in regular mappings, the target property should be left as-is. + * + */ + @IssueKey( "#954") + @ProcessorTest + public void shouldReturnNullForUpdateWithNcvsAlways() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + Domain domain = new Domain(); + domain.setLongs( new HashSet<>() ); + domain.getLongs().add( 10L ); + domain.setStrings( new HashSet<>() ); + domain.getStrings().add( "30" ); + DomainDtoWithNcvsAlwaysMapper.INSTANCE.update( dto, domain ); + + doControlAsserts( domain ); + assertThat( domain.getStrings() ).containsExactly( "30" ); + assertThat( domain.getLongs() ).containsExactly( 10L ); + } + + /** + * Test update with return method ICW presence checker + * + * Similar as in regular mappings, the target property should be left as-is. + * + */ + @IssueKey( "#954") + @ProcessorTest + public void shouldReturnNullForUpdateWithReturnWithNcvsAlways() { + + DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); + Domain domain1 = new Domain(); + domain1.setLongs( new HashSet<>() ); + domain1.getLongs().add( 10L ); + domain1.setStrings( new HashSet<>() ); + domain1.getStrings().add( "30" ); + Domain domain2 = DomainDtoWithNcvsAlwaysMapper.INSTANCE.updateWithReturn( dto, domain1 ); + + assertThat( domain2 ).isSameAs( domain1 ); + doControlAsserts( domain1, domain2 ); + assertThat( domain1.getLongs() ).isEqualTo( domain2.getLongs() ); + assertThat( domain1.getStrings() ).containsExactly( "30" ); + assertThat( domain1.getLongs() ).containsExactly( 10L ); + assertThat( domain2.getStrings() ).containsExactly( "30" ); + assertThat( domain2.getLongs() ).containsExactly( 10L ); + } + + /** + * These assert check if non-null and default mapping is working as expected. + */ + private void doControlAsserts( Domain domain ) { + assertThat( domain.getStringsInitialized() ).containsOnly( "5" ); + assertThat( domain.getLongsInitialized() ).containsOnly( 5L ); + assertThat( domain.getStringsWithDefault() ).containsOnly( "3" ); + } + + /** + * These assert check if non-null and default mapping is working as expected. + */ + private void doControlAsserts( Domain domain1, Domain domain2) { + assertThat( domain1 ).isEqualTo( domain2 ); + assertThat( domain1.getStringsInitialized() ).containsOnly( "5" ); + assertThat( domain1.getLongsInitialized() ).containsOnly( 5L ); + assertThat( domain1.getStringsWithDefault() ).containsOnly( "3" ); + assertThat( domain2.getStringsInitialized() ).containsOnly( "5" ); + assertThat( domain2.getLongsInitialized() ).containsOnly( 5L ); + assertThat( domain2.getStringsWithDefault() ).containsOnly( "3" ); + assertThat( domain1.getStringsInitialized() ).isEqualTo( domain2.getStringsInitialized() ); + assertThat( domain1.getLongsInitialized() ).isEqualTo( domain2.getLongsInitialized() ); + assertThat( domain1.getStringsWithDefault() ).isEqualTo( domain2.getStringsWithDefault() ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Issue931Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Issue931Test.java new file mode 100644 index 0000000000..25c2baf89d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Issue931Test.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.ap.test.bugs._931; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that source.nested == null, leads to target.id == null + * + * @author Sjaak Derksen + */ +@IssueKey( "931" ) +@WithClasses( { Source.class, Nested.class, Target.class, SourceTargetMapper.class } ) +public class Issue931Test { + @ProcessorTest + public void shouldMapNestedNullToNull() { + + Source source = new Source(); + Target target = SourceTargetMapper.MAPPER.toTarget( source ); + + assertThat( target.getId() ).isNull(); + assertThat( target.getIdStr() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Nested.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Nested.java new file mode 100644 index 0000000000..a31265cd14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Nested.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.ap.test.bugs._931; + +public class Nested { + + private long id; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Source.java new file mode 100644 index 0000000000..e3faec32aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Source.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.ap.test.bugs._931; + +public class Source { + + private Nested nested; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/SourceTargetMapper.java new file mode 100644 index 0000000000..c418b778e3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/SourceTargetMapper.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.ap.test.bugs._931; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper { + + SourceTargetMapper MAPPER = Mappers.getMapper( SourceTargetMapper.class ); + + @Mappings({ + @Mapping(target = "id", source = "nested.id"), + @Mapping(target = "idStr", source = "nested.id")}) + Target toTarget(Source s); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Target.java new file mode 100644 index 0000000000..6f1f4b8661 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_931/Target.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.ap.test.bugs._931; + +public class Target { + + private Long id; + private String idStr; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getIdStr() { + return idStr; + } + + public void setIdStr(String idStr) { + this.idStr = idStr; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/CarMapper.java new file mode 100644 index 0000000000..6855f47957 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/CarMapper.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.ap.test.bugs._955; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ap.test.bugs._955.dto.Car; +import org.mapstruct.ap.test.bugs._955.dto.SuperCar; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper(uses = {CustomMapper.class}) +public interface CarMapper { + + CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); + + SuperCar mapCar(Car source, @MappingTarget SuperCar destination); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/CustomMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/CustomMapper.java new file mode 100644 index 0000000000..a2a456ca59 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/CustomMapper.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.ap.test.bugs._955; + +import java.util.Map; + +import org.mapstruct.MappingTarget; +import org.mapstruct.ap.test.bugs._955.dto.Person; + +/** + * + * @author Sjaak Derksen + */ +public class CustomMapper { + + public Map merge(Map source, @MappingTarget Map destination) { + return destination; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/Issue955Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/Issue955Test.java new file mode 100644 index 0000000000..4e03de5373 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/Issue955Test.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.ap.test.bugs._955; + +import org.mapstruct.ap.test.bugs._955.dto.Car; +import org.mapstruct.ap.test.bugs._955.dto.Person; +import org.mapstruct.ap.test.bugs._955.dto.SuperCar; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * + * @author Sjaak Derksen + */ +@IssueKey( "955" ) +@WithClasses( { CarMapper.class, CustomMapper.class, Car.class, SuperCar.class, Person.class } ) +public class Issue955Test { + + @ProcessorTest + public void shouldCompile() { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/Car.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/Car.java new file mode 100644 index 0000000000..5aa4f1e2b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/Car.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.ap.test.bugs._955.dto; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Sjaak Derksen + */ +public class Car { + private Map persons = new HashMap(); + + public Map getPersons() { + return persons; + } + + public void setPersons(Map persons) { + this.persons = persons; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/Person.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/Person.java new file mode 100644 index 0000000000..82adc0cd97 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/Person.java @@ -0,0 +1,14 @@ +/* + * 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.test.bugs._955.dto; + +/** + * + * @author Sjaak Derksen + */ +public class Person { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/SuperCar.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/SuperCar.java new file mode 100644 index 0000000000..43cbfc3651 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_955/dto/SuperCar.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.ap.test.bugs._955.dto; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Sjaak Derksen + */ +public class SuperCar { + private Map persons = new HashMap(); + + public Map getPersons() { + return persons; + } + + public void setPersons(Map persons) { + this.persons = persons; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/CollectionSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/CollectionSource.java new file mode 100644 index 0000000000..23873fabf9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/CollectionSource.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.test.bugs._971; + +import java.util.List; + +/** + * + * @author Filip Hrisafov + */ +public class CollectionSource { + + private List integers; + private List integersCollection; + + public List getIntegers() { + return integers; + } + + public void setIntegers(List integers) { + this.integers = integers; + } + + public List getIntegersCollection() { + return integersCollection; + } + + public void setIntegersCollection(List integersCollection) { + this.integersCollection = integersCollection; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/CollectionTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/CollectionTarget.java new file mode 100644 index 0000000000..26652b15fd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/CollectionTarget.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.test.bugs._971; + +import java.util.Collection; +import java.util.List; + +/** + * + * @author Filip Hrisafov + */ +public class CollectionTarget { + + private List integers; + private Collection integersCollection; + + public List getIntegers() { + return integers; + } + + public void setIntegers(List integers) { + this.integers = integers; + } + + public Collection getIntegersCollection() { + return integersCollection; + } + + public void setIntegersCollection(Collection integersCollection) { + this.integersCollection = integersCollection; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971CollectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971CollectionMapper.java new file mode 100644 index 0000000000..d34d6e3dac --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971CollectionMapper.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.ap.test.bugs._971; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Filip Hrisafov + */ +@Mapper +public interface Issue971CollectionMapper { + + Issue971CollectionMapper INSTANCE = Mappers.getMapper( Issue971CollectionMapper.class ); + + CollectionSource mapTargetToSource(CollectionTarget source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971MapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971MapMapper.java new file mode 100644 index 0000000000..d96e5f008e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971MapMapper.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.ap.test.bugs._971; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Filip Hrisafov + */ +@Mapper +public interface Issue971MapMapper { + + Issue971MapMapper INSTANCE = Mappers.getMapper( Issue971MapMapper.class ); + + MapSource mapTargetToSource(MapTarget source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971Test.java new file mode 100644 index 0000000000..fe178f815b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/Issue971Test.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.ap.test.bugs._971; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author Filip Hrisafov + * + */ +@IssueKey("971") +public class Issue971Test { + + @ProcessorTest + @WithClasses({ CollectionSource.class, CollectionTarget.class, Issue971CollectionMapper.class }) + public void shouldCompileForCollections() { } + + @ProcessorTest + @WithClasses({ MapSource.class, MapTarget.class, Issue971MapMapper.class }) + public void shouldCompileForMaps() { } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/MapSource.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/MapSource.java new file mode 100644 index 0000000000..c56973f23c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/MapSource.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.test.bugs._971; + +import java.util.Map; + +/** + * + * @author Filip Hrisafov + */ +public class MapSource { + + private Map integersSortedMap; + private Map integersBaseMap; + + public Map getIntegersSortedMap() { + return integersSortedMap; + } + + public void setIntegersSortedMap(Map integersSortedMap) { + this.integersSortedMap = integersSortedMap; + } + + public Map getIntegersBaseMap() { + return integersBaseMap; + } + + public void setIntegersBaseMap(Map integersBaseMap) { + this.integersBaseMap = integersBaseMap; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/MapTarget.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/MapTarget.java new file mode 100644 index 0000000000..43f8abf689 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_971/MapTarget.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.test.bugs._971; + +import java.util.Map; +import java.util.SortedMap; + +/** + * + * @author Filip Hrisafov + */ +public class MapTarget { + + private SortedMap integersSortedMap; + private Map integersBaseMap; + + public SortedMap getIntegersSortedMap() { + return integersSortedMap; + } + + public void setIntegersSortedMap(SortedMap integersSortedMap) { + this.integersSortedMap = integersSortedMap; + } + + public Map getIntegersBaseMap() { + return integersBaseMap; + } + + public void setIntegersBaseMap(Map integersBaseMap) { + this.integersBaseMap = integersBaseMap; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractBuilderTest.java new file mode 100644 index 0000000000..47a534d20a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractBuilderTest.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.test.builder.abstractBuilder; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This test is for abstract builders, where some of the target properties are written by the abstract + * builder class, and some of the properties are written by the concrete builder implementation. + */ +@WithClasses({ + AbstractProductBuilder.class, + AbstractImmutableProduct.class, + ImmutableProduct.class, + ProductDto.class, + ProductMapper.class +}) +public class AbstractBuilderTest { + + /** + * This test verifies that: + * WHEN - a mapping method's return type is an immutable/built target, AND + * - the mapped properties are split between the abstract and concrete builder, AND + * THEN + * - the builder is "discovered" + * - all values are mapped + * - all values are set properly + */ + @ProcessorTest + public void testThatAbstractBuilderMapsAllProperties() { + ImmutableProduct product = ProductMapper.INSTANCE.fromMutable( new ProductDto( "router", 31 ) ); + + assertThat( product.getPrice() ).isEqualTo( 31 ); + assertThat( product.getName() ).isEqualTo( "router" ); + } + + @ProcessorTest + public void testThatAbstractBuilderReverseMapsAllProperties() { + ProductDto product = ProductMapper.INSTANCE.fromImmutable( ImmutableProduct.builder() + .price( 31000 ) + .name( "car" ) + .build() ); + + assertThat( product.getPrice() ).isEqualTo( 31000 ); + assertThat( product.getName() ).isEqualTo( "car" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractImmutableProduct.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractImmutableProduct.java new file mode 100644 index 0000000000..f8b9421ebc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractImmutableProduct.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.ap.test.builder.abstractBuilder; + +/** + * @author Filip Hrisafov + */ +public abstract class AbstractImmutableProduct { + + private final String name; + + public AbstractImmutableProduct(AbstractProductBuilder builder) { + this.name = builder.name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractProductBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractProductBuilder.java new file mode 100644 index 0000000000..41ed9d43e5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/AbstractProductBuilder.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.ap.test.builder.abstractBuilder; + +public abstract class AbstractProductBuilder { + + protected String name; + + public AbstractProductBuilder name(String name) { + this.name = name; + return this; + } + + abstract T build(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ImmutableProduct.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ImmutableProduct.java new file mode 100644 index 0000000000..d3b710cddc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ImmutableProduct.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.test.builder.abstractBuilder; + +public class ImmutableProduct extends AbstractImmutableProduct { + + private final Integer price; + + public ImmutableProduct(ImmutableProductBuilder builder) { + super( builder ); + this.price = builder.price; + } + + public static ImmutableProductBuilder builder() { + return new ImmutableProductBuilder(); + } + + public Integer getPrice() { + return price; + } + + public static class ImmutableProductBuilder extends AbstractProductBuilder { + private Integer price; + + public ImmutableProductBuilder price(Integer price) { + this.price = price; + return this; + } + + @Override + public ImmutableProduct build() { + return new ImmutableProduct( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ProductDto.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ProductDto.java new file mode 100644 index 0000000000..0e3eaaae6b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ProductDto.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.test.builder.abstractBuilder; + +public class ProductDto { + private String name; + private Integer price; + + public ProductDto() { + } + + public ProductDto(String name, Integer price) { + this.name = name; + this.price = price; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getPrice() { + return price; + } + + public void setPrice(Integer price) { + this.price = price; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ProductMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ProductMapper.java new file mode 100644 index 0000000000..877c18746d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractBuilder/ProductMapper.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.ap.test.builder.abstractBuilder; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ProductMapper { + + ProductMapper INSTANCE = Mappers.getMapper( ProductMapper.class ); + + ImmutableProduct fromMutable(ProductDto source); + + ProductDto fromImmutable(ImmutableProduct source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/AbstractGenericTargetBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/AbstractGenericTargetBuilderTest.java new file mode 100644 index 0000000000..2b77353175 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/AbstractGenericTargetBuilderTest.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.test.builder.abstractGenericTarget; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that abstract builders work when mapping to an abstract property type. + */ +@WithClasses({ + Child.class, + Parent.class, + ChildSource.class, + ImmutableChild.class, + ImmutableParent.class, + MutableChild.class, + MutableParent.class, + ParentSource.class, + ParentMapper.class +}) +public class AbstractGenericTargetBuilderTest { + + @ProcessorTest + public void testAbstractTargetMapper() { + ParentSource parent = new ParentSource(); + parent.setCount( 4 ); + parent.setChild( new ChildSource( "Phineas" ) ); + parent.setNonGenericChild( new ChildSource( "Ferb" ) ); + + // transform + Parent immutableParent = ParentMapper.INSTANCE.toImmutable( parent ); + assertThat( immutableParent.getCount() ).isEqualTo( 4 ); + assertThat( immutableParent.getChild().getName() ).isEqualTo( "Phineas" ); + assertThat( immutableParent.getNonGenericChild() ) + .isNotNull() + .isInstanceOf( ImmutableChild.class ); + assertThat( immutableParent.getNonGenericChild().getName() ).isEqualTo( "Ferb" ); + + Parent mutableParent = ParentMapper.INSTANCE.toMutable( parent ); + + assertThat( mutableParent.getCount() ).isEqualTo( 4 ); + assertThat( mutableParent.getChild().getName() ).isEqualTo( "Phineas" ); + assertThat( mutableParent.getNonGenericChild() ) + .isNotNull() + .isInstanceOf( ImmutableChild.class ); + assertThat( mutableParent.getNonGenericChild().getName() ).isEqualTo( "Ferb" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/Child.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/Child.java new file mode 100644 index 0000000000..a136448b18 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/Child.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.ap.test.builder.abstractGenericTarget; + +public interface Child { + String getName(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ChildSource.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ChildSource.java new file mode 100644 index 0000000000..6a23998adf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ChildSource.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.ap.test.builder.abstractGenericTarget; + +public class ChildSource { + private String name; + + public ChildSource() { + } + + public ChildSource(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ImmutableChild.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ImmutableChild.java new file mode 100644 index 0000000000..23dc617408 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ImmutableChild.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.test.builder.abstractGenericTarget; + +public class ImmutableChild implements Child { + private final String name; + + private ImmutableChild(ImmutableChild.Builder builder) { + this.name = builder.name; + } + + public static ImmutableChild.Builder builder() { + return new ImmutableChild.Builder(); + } + + public String getName() { + return name; + } + + public static class Builder { + private String name; + + public ImmutableChild.Builder name(String name) { + this.name = name; + return this; + } + + public ImmutableChild build() { + return new ImmutableChild( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ImmutableParent.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ImmutableParent.java new file mode 100644 index 0000000000..f0877844a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ImmutableParent.java @@ -0,0 +1,63 @@ +/* + * 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.test.builder.abstractGenericTarget; + +public class ImmutableParent implements Parent { + private final int count; + private final ImmutableChild child; + private final Child nonGenericChild; + + public ImmutableParent(Builder builder) { + this.count = builder.count; + this.child = builder.child; + this.nonGenericChild = builder.nonGenericChild; + } + + public static Builder builder() { + return new Builder(); + } + + @Override + public Child getNonGenericChild() { + return nonGenericChild; + } + + @Override + public int getCount() { + return count; + } + + @Override + public ImmutableChild getChild() { + return child; + } + + public static class Builder { + private int count; + private ImmutableChild child; + private Child nonGenericChild; + + public Builder count(int count) { + this.count = count; + return this; + } + + public Builder nonGenericChild(Child nonGenericChild) { + this.nonGenericChild = nonGenericChild; + return this; + } + + public Builder child(ImmutableChild child) { + this.child = child; + return this; + } + + public ImmutableParent build() { + return new ImmutableParent( this ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/MutableChild.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/MutableChild.java new file mode 100644 index 0000000000..e43417d753 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/MutableChild.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.ap.test.builder.abstractGenericTarget; + +public class MutableChild implements Child { + private String name; + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/MutableParent.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/MutableParent.java new file mode 100644 index 0000000000..8ad97dbb62 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/MutableParent.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.ap.test.builder.abstractGenericTarget; + +public class MutableParent implements Parent { + private int count; + private ImmutableChild child; + private Child nonGenericChild; + + @Override + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + @Override + public ImmutableChild getChild() { + return child; + } + + public void setChild(ImmutableChild child) { + this.child = child; + } + + @Override + public Child getNonGenericChild() { + return nonGenericChild; + } + + public void setNonGenericChild(Child nonGenericChild) { + this.nonGenericChild = nonGenericChild; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/Parent.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/Parent.java new file mode 100644 index 0000000000..8d3f548973 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/Parent.java @@ -0,0 +1,14 @@ +/* + * 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.test.builder.abstractGenericTarget; + +public interface Parent { + int getCount(); + + T getChild(); + + Child getNonGenericChild(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ParentMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ParentMapper.java new file mode 100644 index 0000000000..0fa5f5633f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ParentMapper.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.ap.test.builder.abstractGenericTarget; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ParentMapper { + + ParentMapper INSTANCE = Mappers.getMapper( ParentMapper.class ); + + ImmutableParent toImmutable(ParentSource parentSource); + + MutableParent toMutable(ParentSource parentSource); + + /** + * This method allows mapstruct to successfully write to {@link ImmutableParent#nonGenericChild} + * by providing a concrete class to convert to. + */ + ImmutableChild toChild(ChildSource child); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ParentSource.java b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ParentSource.java new file mode 100644 index 0000000000..56e6da533f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/abstractGenericTarget/ParentSource.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.test.builder.abstractGenericTarget; + +public class ParentSource { + private int count; + private ChildSource child; + private ChildSource nonGenericChild; + + public ChildSource getNonGenericChild() { + return nonGenericChild; + } + + public void setNonGenericChild(ChildSource nonGenericChild) { + this.nonGenericChild = nonGenericChild; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public ChildSource getChild() { + return child; + } + + public void setChild(ChildSource child) { + this.child = child; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderFactoryMapper.java new file mode 100644 index 0000000000..2846e17795 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderFactoryMapper.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.ap.test.builder.factory; + +import org.mapstruct.Mapper; +import org.mapstruct.ObjectFactory; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public abstract class BuilderFactoryMapper { + + public static final BuilderFactoryMapper INSTANCE = Mappers.getMapper( BuilderFactoryMapper.class ); + + public abstract Person map(PersonDto source); + + @ObjectFactory + public Person.PersonBuilder personBuilder() { + return new Person.PersonBuilder( "Factory with @ObjectFactory" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderFactoryMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderFactoryMapperTest.java new file mode 100644 index 0000000000..5ec38b2d0a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderFactoryMapperTest.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.ap.test.builder.factory; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + BuilderFactoryMapper.class, + BuilderImplicitFactoryMapper.class, + Person.class, + PersonDto.class +}) +public class BuilderFactoryMapperTest { + + @ProcessorTest + public void shouldUseBuilderFactory() { + Person person = BuilderFactoryMapper.INSTANCE.map( new PersonDto( "Filip" ) ); + + assertThat( person.getName() ).isEqualTo( "Filip" ); + assertThat( person.getSource() ).isEqualTo( "Factory with @ObjectFactory" ); + } + + @ProcessorTest + public void shouldUseImplicitBuilderFactory() { + Person person = BuilderImplicitFactoryMapper.INSTANCE.map( new PersonDto( "Filip" ) ); + + assertThat( person.getName() ).isEqualTo( "Filip" ); + assertThat( person.getSource() ).isEqualTo( "Implicit Factory" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderImplicitFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderImplicitFactoryMapper.java new file mode 100644 index 0000000000..916b3cac32 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/BuilderImplicitFactoryMapper.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.ap.test.builder.factory; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public abstract class BuilderImplicitFactoryMapper { + + public static final BuilderImplicitFactoryMapper INSTANCE = Mappers.getMapper( BuilderImplicitFactoryMapper.class ); + + public abstract Person map(PersonDto source); + + public Person.PersonBuilder personBuilder() { + return new Person.PersonBuilder( "Implicit Factory" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/factory/Person.java b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/Person.java new file mode 100644 index 0000000000..f371c1566c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/Person.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.ap.test.builder.factory; + +/** + * @author Filip Hrisafov + */ +public class Person { + + private final String name; + private final String source; + + protected Person(PersonBuilder builder) { + this.name = builder.name; + this.source = builder.source; + } + + public String getName() { + return name; + } + + public String getSource() { + return source; + } + + public static PersonBuilder builder() { + throw new UnsupportedOperationException( "Factory should be used" ); + } + + public static class PersonBuilder { + private String name; + private final String source; + + public PersonBuilder(String source) { + this.source = source; + } + + public PersonBuilder name(String name) { + this.name = name; + return this; + } + + public Person create() { + return new Person( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/factory/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/PersonDto.java new file mode 100644 index 0000000000..ac326e524a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/factory/PersonDto.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.ap.test.builder.factory; + +/** + * @author Filip Hrisafov + */ +public class PersonDto { + + private String name; + + public PersonDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BaseDto.java b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BaseDto.java new file mode 100644 index 0000000000..3ababeec92 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BaseDto.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.ap.test.builder.ignore; + +/** + * @author Filip Hrisafov + */ +public class BaseDto { + + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BaseEntity.java b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BaseEntity.java new file mode 100644 index 0000000000..7835305be5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BaseEntity.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.ap.test.builder.ignore; + +/** + * @author Filip Hrisafov + */ +public class BaseEntity { + + private final Long id; + + public BaseEntity(Builder builder) { + this.id = builder.id; + } + + public Long getId() { + return id; + } + + public static Builder baseBuilder() { + return new Builder(); + } + + public static class Builder { + private Long id; + + public Builder id(Long id) { + this.id = id; + return this; + } + + public BaseEntity createBase() { + return new BaseEntity( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringMapper.java new file mode 100644 index 0000000000..9d26b94327 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringMapper.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.ap.test.builder.ignore; + +import org.mapstruct.BeanMapping; +import org.mapstruct.InheritConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = BuilderIgnoringMappingConfig.class) +public interface BuilderIgnoringMapper { + + BuilderIgnoringMapper INSTANCE = Mappers.getMapper( BuilderIgnoringMapper.class ); + + @InheritConfiguration(name = "mapBase") + @Mapping( target = "lastName" ) + Person mapWithIgnoringBase(PersonDto source); + + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "name", source = "name") + Person mapOnlyWithExplicit(PersonDto source); + + Person mapAll(PersonDto source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringMappingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringMappingConfig.java new file mode 100644 index 0000000000..1cd9be99b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringMappingConfig.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.ap.test.builder.ignore; + +import org.mapstruct.BeanMapping; +import org.mapstruct.MapperConfig; + +/** + * @author Filip Hrisafov + */ +@MapperConfig +public interface BuilderIgnoringMappingConfig { + + @BeanMapping(ignoreByDefault = true) + BaseEntity mapBase(BaseDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringTest.java new file mode 100644 index 0000000000..5be9d50ff6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/BuilderIgnoringTest.java @@ -0,0 +1,70 @@ +/* + * 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.test.builder.ignore; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1452") +@WithClasses({ + BaseDto.class, + BaseEntity.class, + BuilderIgnoringMapper.class, + BuilderIgnoringMappingConfig.class, + Person.class, + PersonDto.class +}) +public class BuilderIgnoringTest { + + @ProcessorTest + @IssueKey( "1933" ) + public void shouldIgnoreBase() { + PersonDto source = new PersonDto(); + source.setId( 100L ); + source.setName( "John" ); + source.setLastName( "Doe" ); + + Person target = BuilderIgnoringMapper.INSTANCE.mapWithIgnoringBase( source ); + + assertThat( target.getId() ).isNull(); + assertThat( target.getName() ).isNull(); + assertThat( target.getLastName() ).isEqualTo( "Doe" ); + } + + @ProcessorTest + public void shouldMapOnlyExplicit() { + PersonDto source = new PersonDto(); + source.setId( 100L ); + source.setName( "John" ); + source.setLastName( "Doe" ); + + Person target = BuilderIgnoringMapper.INSTANCE.mapOnlyWithExplicit( source ); + + assertThat( target.getId() ).isNull(); + assertThat( target.getName() ).isEqualTo( "John" ); + assertThat( target.getLastName() ).isNull(); + } + + @ProcessorTest + public void shouldMapAll() { + PersonDto source = new PersonDto(); + source.setId( 100L ); + source.setName( "John" ); + source.setLastName( "Doe" ); + + Person target = BuilderIgnoringMapper.INSTANCE.mapAll( source ); + + assertThat( target.getId() ).isEqualTo( 100L ); + assertThat( target.getName() ).isEqualTo( "John" ); + assertThat( target.getLastName() ).isEqualTo( "Doe" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/Person.java b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/Person.java new file mode 100644 index 0000000000..7ab599bdf4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/Person.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.test.builder.ignore; + +/** + * @author Filip Hrisafov + */ +public class Person extends BaseEntity { + + private final String name; + private final String lastName; + + public Person(Builder builder) { + super( builder ); + this.name = builder.name; + this.lastName = builder.lastName; + } + + public String getName() { + return name; + } + + public String getLastName() { + return lastName; + } + + public static Builder builder() { + return new Builder(); + + } + + public static class Builder extends BaseEntity.Builder { + private String name; + private String lastName; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder lastName(String lastName) { + this.lastName = lastName; + return this; + } + + public Person create() { + return new Person( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/PersonDto.java new file mode 100644 index 0000000000..31d3ac08f1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/ignore/PersonDto.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.ap.test.builder.ignore; + +/** + * @author Filip Hrisafov + */ +public class PersonDto extends BaseDto { + + private String name; + private String lastName; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java new file mode 100644 index 0000000000..3d5fb7e533 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/BuilderLifecycleCallbacksTest.java @@ -0,0 +1,58 @@ +/* + * 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.test.builder.lifecycle; + +import java.util.Arrays; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey( "1433" ) +@WithClasses( { + Item.class, + ItemDto.class, + MappingContext.class, + Order.class, + OrderDto.class, + OrderMapper.class +} ) +public class BuilderLifecycleCallbacksTest { + + @ProcessorTest + public void lifecycleMethodsShouldBeInvoked() { + OrderDto source = new OrderDto(); + source.setCreator( "Filip" ); + ItemDto item1 = new ItemDto(); + item1.setName( "Laptop" ); + ItemDto item2 = new ItemDto(); + item2.setName( "Keyboard" ); + source.setItems( Arrays.asList( item1, item2 ) ); + MappingContext context = new MappingContext(); + + OrderMapper.INSTANCE.map( source, context ); + + assertThat( context.getInvokedMethods() ) + .contains( + "beforeWithoutParameters", + "beforeWithTargetType", + "beforeWithBuilderTargetType", + "beforeWithBuilderTarget", + "afterWithoutParameters", + "afterWithBuilderTargetType", + "afterWithBuilderTarget", + "afterWithBuilderTargetReturningTarget", + "afterWithTargetType", + "afterWithTarget", + "afterWithTargetReturningTarget" + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/Item.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/Item.java new file mode 100644 index 0000000000..19da018e85 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/Item.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.ap.test.builder.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class Item { + + private final String name; + + Item(Builder builder) { + this.name = builder.name; + } + + public String getName() { + return name; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String name; + + public Builder name(String name) { + this.name = name; + return this; + } + + public Item create() { + return new Item( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/ItemDto.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/ItemDto.java new file mode 100644 index 0000000000..664d99c966 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/ItemDto.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.ap.test.builder.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class ItemDto { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java new file mode 100644 index 0000000000..2065bad478 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/MappingContext.java @@ -0,0 +1,91 @@ +/* + * 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.test.builder.lifecycle; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +/** + * @author Filip Hrisafov + */ +public class MappingContext { + + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + public void beforeWithoutParameters() { + invokedMethods.add( "beforeWithoutParameters" ); + } + + @BeforeMapping + public void beforeWithTargetType(OrderDto source, @TargetType Class orderClass) { + invokedMethods.add( "beforeWithTargetType" ); + } + + @BeforeMapping + public void beforeWithBuilderTargetType(OrderDto source, @TargetType Class builderClass) { + invokedMethods.add( "beforeWithBuilderTargetType" ); + } + + @BeforeMapping + public void beforeWithTarget(OrderDto source, @MappingTarget Order order) { + invokedMethods.add( "beforeWithTarget" ); + } + + @BeforeMapping + public void beforeWithBuilderTarget(OrderDto source, @MappingTarget Order.Builder orderBuilder) { + invokedMethods.add( "beforeWithBuilderTarget" ); + } + + @AfterMapping + public void afterWithoutParameters() { + invokedMethods.add( "afterWithoutParameters" ); + } + + @AfterMapping + public void afterWithTargetType(OrderDto source, @TargetType Class orderClass) { + invokedMethods.add( "afterWithTargetType" ); + } + + @AfterMapping + public void afterWithBuilderTargetType(OrderDto source, @TargetType Class builderClass) { + invokedMethods.add( "afterWithBuilderTargetType" ); + } + + @AfterMapping + public void afterWithTarget(OrderDto source, @MappingTarget Order order) { + invokedMethods.add( "afterWithTarget" ); + } + + @AfterMapping + public void afterWithBuilderTarget(OrderDto source, @MappingTarget Order.Builder orderBuilder) { + invokedMethods.add( "afterWithBuilderTarget" ); + } + + @AfterMapping + public Order afterWithBuilderTargetReturningTarget(@MappingTarget Order.Builder orderBuilder) { + invokedMethods.add( "afterWithBuilderTargetReturningTarget" ); + + // return null, so that @AfterMapping methods on the finalized object will be called in the tests + return null; + } + + @AfterMapping + public Order afterWithTargetReturningTarget(@MappingTarget Order order) { + invokedMethods.add( "afterWithTargetReturningTarget" ); + + return order; + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/Order.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/Order.java new file mode 100644 index 0000000000..54316bf774 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/Order.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.ap.test.builder.lifecycle; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Order { + + private final List items; + private final String creator; + + public Order(Builder builder) { + this.items = new ArrayList<>( builder.items ); + this.creator = builder.creator; + } + + public List getItems() { + return items; + } + + public String getCreator() { + return creator; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private List items = new ArrayList<>(); + private String creator; + + public Builder items(List items) { + this.items = items; + return this; + } + + public Order create() { + return new Order( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/OrderDto.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/OrderDto.java new file mode 100644 index 0000000000..5da6578a74 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/OrderDto.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.ap.test.builder.lifecycle; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class OrderDto { + + private List items; + private String creator; + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/OrderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/OrderMapper.java new file mode 100644 index 0000000000..12dab91ce2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/lifecycle/OrderMapper.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.ap.test.builder.lifecycle; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OrderMapper { + + OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class ); + + Order map(OrderDto source, @Context MappingContext context); + + Item map(ItemDto source, @Context MappingContext context); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java new file mode 100644 index 0000000000..5d0ff919ec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/BuilderInfoTargetTest.java @@ -0,0 +1,110 @@ +/* + * 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.test.builder.mappingTarget.simple; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + MutableTarget.class, + SimpleMutableSource.class, + SimpleImmutableTarget.class, + SimpleBuilderMapper.class +}) +public class BuilderInfoTargetTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void testSimpleImmutableBuilderHappyPath() { + SimpleMutableSource source = new SimpleMutableSource(); + source.setAge( 3 ); + source.setFullName( "Bob" ); + SimpleImmutableTarget targetObject = SimpleBuilderMapper.INSTANCE.toImmutable( + source, + SimpleImmutableTarget.builder() + ); + assertThat( targetObject.getAge() ).isEqualTo( 3 ); + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + } + + @ProcessorTest + @IssueKey("1752") + public void testSimpleImmutableBuilderFromNullSource() { + SimpleImmutableTarget targetObject = SimpleBuilderMapper.INSTANCE.toImmutable( + null, + SimpleImmutableTarget.builder().age( 3 ).name( "Bob" ) + ); + assertThat( targetObject ).isNotNull(); + assertThat( targetObject.getAge() ).isEqualTo( 3 ); + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + } + + @ProcessorTest + public void testMutableTargetWithBuilder() { + SimpleMutableSource source = new SimpleMutableSource(); + source.setAge( 20 ); + source.setFullName( "Filip" ); + MutableTarget target = SimpleBuilderMapper.INSTANCE.toMutableTarget( source ); + assertThat( target.getAge() ).isEqualTo( 20 ); + assertThat( target.getName() ).isEqualTo( "Filip" ); + assertThat( target.getSource() ).isEqualTo( "Builder" ); + } + + @ProcessorTest + public void testUpdateMutableWithBuilder() { + SimpleMutableSource source = new SimpleMutableSource(); + source.setAge( 20 ); + source.setFullName( "Filip" ); + MutableTarget target = new MutableTarget(); + target.setAge( 10 ); + target.setName( "Fil" ); + + assertThat( target.getAge() ).isEqualTo( 10 ); + assertThat( target.getName() ).isEqualTo( "Fil" ); + assertThat( target.getSource() ).isEqualTo( "Empty constructor" ); + + SimpleBuilderMapper.INSTANCE.updateMutableTarget( source, target ); + assertThat( target.getAge() ).isEqualTo( 20 ); + assertThat( target.getName() ).isEqualTo( "Filip" ); + assertThat( target.getSource() ).isEqualTo( "Empty constructor" ); + } + + @ProcessorTest + @WithClasses( { + SimpleImmutableUpdateMapper.class + } ) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SimpleImmutableUpdateMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 18, + message = "No target property found for target \"SimpleImmutableTarget\"."), + }) + + public void updatingTargetWithNoSettersShouldNotFail() { + + SimpleMutableSource source = new SimpleMutableSource(); + source.setAge( 10 ); + + SimpleImmutableTarget target = SimpleImmutableTarget.builder() + .age( 20 ) + .build(); + + assertThat( target.getAge() ).isEqualTo( 20 ); + SimpleImmutableUpdateMapper.INSTANCE.toImmutable( source, target ); + assertThat( target.getAge() ).isEqualTo( 20 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/MutableTarget.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/MutableTarget.java new file mode 100644 index 0000000000..02668f5fff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/MutableTarget.java @@ -0,0 +1,65 @@ +/* + * 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.test.builder.mappingTarget.simple; + +public class MutableTarget { + private String name; + private int age; + private String source; + + public MutableTarget() { + this.source = "Empty constructor"; + } + + MutableTarget(Builder builder) { + this.name = builder.name; + this.age = builder.age; + this.source = "Builder"; + } + + public static Builder builder() { + return new Builder(); + } + + 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 String getSource() { + return source; + } + + public static class Builder { + private String name; + private int age; + + public Builder age(int age) { + this.age = age; + return this; + } + + public MutableTarget build() { + return new MutableTarget( this ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java new file mode 100644 index 0000000000..6185daee97 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleBuilderMapper.java @@ -0,0 +1,32 @@ +/* + * 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.test.builder.mappingTarget.simple; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SimpleBuilderMapper { + + SimpleBuilderMapper INSTANCE = Mappers.getMapper( SimpleBuilderMapper.class ); + + @Mapping(target = "builder.name", source = "source.fullName") + SimpleImmutableTarget toImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget.Builder builder); + + @Mapping(target = "builder.name", source = "source.fullName") + void updateImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget.Builder builder); + + @Mapping(target = "name", source = "fullName") + SimpleImmutableTarget toImmutable(SimpleMutableSource source); + + @Mapping(target = "name", source = "fullName") + MutableTarget toMutableTarget(SimpleMutableSource simpleMutableSource); + + @Mapping(target = "name", source = "fullName") + void updateMutableTarget(SimpleMutableSource simpleMutableSource, @MappingTarget MutableTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableTarget.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableTarget.java new file mode 100644 index 0000000000..cc7b103dc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableTarget.java @@ -0,0 +1,47 @@ +/* + * 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.test.builder.mappingTarget.simple; + +public class SimpleImmutableTarget { + private final String name; + private final int age; + + SimpleImmutableTarget(Builder builder) { + this.name = builder.name; + this.age = builder.age; + } + + public static Builder builder() { + return new Builder(); + } + + public int getAge() { + return age; + } + + public String getName() { + return name; + } + + public static class Builder { + private String name; + private int age; + + public Builder age(int age) { + this.age = age; + return this; + } + + public SimpleImmutableTarget build() { + return new SimpleImmutableTarget( this ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.java new file mode 100644 index 0000000000..c6eccc96c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleImmutableUpdateMapper.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.ap.test.builder.mappingTarget.simple; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SimpleImmutableUpdateMapper { + + SimpleImmutableUpdateMapper INSTANCE = Mappers.getMapper( SimpleImmutableUpdateMapper.class ); + + // This method is fine as if the mapping target has setters it would use them, otherwise it won't + void toImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleMutableSource.java b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleMutableSource.java new file mode 100644 index 0000000000..9befd215a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/mappingTarget/simple/SimpleMutableSource.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.ap.test.builder.mappingTarget.simple; + +public class SimpleMutableSource { + private String fullName; + private int age; + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderConfigDefinedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderConfigDefinedMapper.java new file mode 100644 index 0000000000..3a2d6a2c60 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderConfigDefinedMapper.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.ap.test.builder.multiple; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builder.multiple.build.Process; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = BuilderMapperConfig.class) +public interface BuilderConfigDefinedMapper { + + BuilderConfigDefinedMapper INSTANCE = Mappers.getMapper( BuilderConfigDefinedMapper.class ); + + Process map(Source source); + + @BeanMapping(builder = @Builder(buildMethod = "wrongCreate")) + Process wrongMap(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderDefinedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderDefinedMapper.java new file mode 100644 index 0000000000..2cccbc0afb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderDefinedMapper.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.ap.test.builder.multiple; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builder.multiple.build.Process; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(builder = @Builder(buildMethod = "create")) +public interface BuilderDefinedMapper { + + BuilderDefinedMapper INSTANCE = Mappers.getMapper( BuilderDefinedMapper.class ); + + Process map(Source source); + + @BeanMapping(builder = @Builder(buildMethod = "wrongCreate")) + Process wrongMap(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderMapperConfig.java new file mode 100644 index 0000000000..2bd52af4b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/BuilderMapperConfig.java @@ -0,0 +1,16 @@ +/* + * 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.test.builder.multiple; + +import org.mapstruct.Builder; +import org.mapstruct.MapperConfig; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(builder = @Builder(buildMethod = "create")) +public interface BuilderMapperConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/DefaultBuildMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/DefaultBuildMethodMapper.java new file mode 100644 index 0000000000..8e66255d59 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/DefaultBuildMethodMapper.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.ap.test.builder.multiple; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface DefaultBuildMethodMapper { + + DefaultBuildMethodMapper INSTANCE = Mappers.getMapper( DefaultBuildMethodMapper.class ); + + Task map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/ErroneousMoreThanOneBuildMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/ErroneousMoreThanOneBuildMethodMapper.java new file mode 100644 index 0000000000..1c591f8e48 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/ErroneousMoreThanOneBuildMethodMapper.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.ap.test.builder.multiple; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builder.multiple.build.Process; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousMoreThanOneBuildMethodMapper { + + Process map(Source source); + + @BeanMapping(builder = @Builder(buildMethod = "missingBuild")) + Process map2(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper.java new file mode 100644 index 0000000000..34194e5025 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper.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.ap.test.builder.multiple; + +import org.mapstruct.Builder; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builder.multiple.build.Process; + +/** + * @author Filip Hrisafov + */ +@Mapper(builder = @Builder(buildMethod = "mapperBuild")) +public interface ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper { + + Process map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/MultipleBuilderMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/MultipleBuilderMapperTest.java new file mode 100644 index 0000000000..825794d6d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/MultipleBuilderMapperTest.java @@ -0,0 +1,137 @@ +/* + * 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.test.builder.multiple; + +import org.mapstruct.ap.test.builder.multiple.build.Process; +import org.mapstruct.ap.test.builder.multiple.builder.Case; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("1479") +@WithClasses({ + Process.class, + Case.class, + Task.class, + Source.class +}) +public class MultipleBuilderMapperTest { + + @WithClasses({ + ErroneousMoreThanOneBuildMethodMapper.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = ErroneousMoreThanOneBuildMethodMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "No build method \"build\" found in \"org.mapstruct.ap.test.builder.multiple.build.Process" + + ".Builder\" for \"org.mapstruct.ap.test.builder.multiple.build.Process\". Found methods: " + + "\"wrongCreate(), create()\". Consider to add @Builder in order to select the correct build method." + ), + @Diagnostic( + type = ErroneousMoreThanOneBuildMethodMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 21, + message = "No build method \"missingBuild\" found in \"org.mapstruct.ap.test.builder.multiple.build" + + ".Process.Builder\" for \"org.mapstruct.ap.test.builder.multiple.build.Process\". Found methods: " + + "\"wrongCreate(), create()\"." + ) + }) + @ProcessorTest + public void moreThanOneBuildMethod() { + } + + @WithClasses({ + ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 18, + message = + "No build method \"mapperBuild\" found in \"org.mapstruct.ap.test.builder.multiple.build.Process" + + ".Builder\" for \"org.mapstruct.ap.test.builder.multiple.build.Process\". Found methods: " + + "\"wrongCreate(), create()\"." + ) + }) + @ProcessorTest + public void moreThanOneBuildMethodDefinedOnMapper() { + } + + @WithClasses({ + BuilderDefinedMapper.class + }) + @ProcessorTest + public void builderMappingDefined() { + Process map = BuilderDefinedMapper.INSTANCE.map( new Source( "map" ) ); + Process wrongMap = BuilderDefinedMapper.INSTANCE.wrongMap( new Source( "wrongMap" ) ); + + assertThat( map.getBuildMethod() ).isEqualTo( "create" ); + assertThat( wrongMap.getBuildMethod() ).isEqualTo( "wrongCreate" ); + } + + @WithClasses({ + BuilderMapperConfig.class, + BuilderConfigDefinedMapper.class + }) + @ProcessorTest + public void builderMappingMapperConfigDefined() { + Process map = BuilderConfigDefinedMapper.INSTANCE.map( new Source( "map" ) ); + Process wrongMap = BuilderConfigDefinedMapper.INSTANCE.wrongMap( new Source( "wrongMap" ) ); + + assertThat( map.getBuildMethod() ).isEqualTo( "create" ); + assertThat( wrongMap.getBuildMethod() ).isEqualTo( "wrongCreate" ); + } + + @WithClasses({ + TooManyBuilderCreationMethodsMapper.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + type = Case.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 11, + message = "More than one builder creation method for \"org.mapstruct.ap.test.builder.multiple.builder" + + ".Case\". Found methods: \"wrongBuilder(), builder()\". Builder will not be used. Consider " + + "implementing a custom BuilderProvider SPI." + ) + }) + @ProcessorTest + public void tooManyBuilderCreationMethods() { + Case caseTarget = TooManyBuilderCreationMethodsMapper.INSTANCE.map( new Source( "test" ) ); + + assertThat( caseTarget ).isNotNull(); + assertThat( caseTarget.getName() ).isEqualTo( "test" ); + assertThat( caseTarget.getBuilderCreationMethod() ).isNull(); + assertThat( caseTarget.getBuildMethod() ).isEqualTo( "constructor" ); + } + + @WithClasses( { + DefaultBuildMethodMapper.class + } ) + @ProcessorTest + public void defaultBuildMethod() { + Task task = DefaultBuildMethodMapper.INSTANCE.map( new Source( "test" ) ); + + assertThat( task ).isNotNull(); + assertThat( task.getName() ).isEqualTo( "test" ); + assertThat( task.getBuilderCreationMethod() ).isEqualTo( "builder" ); + assertThat( task.getBuildMethod() ).isEqualTo( "build" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/Source.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/Source.java new file mode 100644 index 0000000000..04701b8472 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/Source.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.ap.test.builder.multiple; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final String name; + + public Source(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/Task.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/Task.java new file mode 100644 index 0000000000..67317d2edd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/Task.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.ap.test.builder.multiple; + +/** + * @author Filip Hrisafov + */ +public class Task { + + private final String builderCreationMethod; + private final String buildMethod; + private String name; + + public Task() { + this.builderCreationMethod = null; + this.buildMethod = "constructor"; + } + + public Task(Builder builder, String buildMethod) { + this.builderCreationMethod = builder.builderCreationMethod; + this.buildMethod = buildMethod; + this.name = builder.name; + } + + public String getBuilderCreationMethod() { + return builderCreationMethod; + } + + public String getBuildMethod() { + return buildMethod; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public static Builder builder() { + return new Builder( "builder" ); + } + + public static class Builder { + + private String name; + private String builderCreationMethod; + + protected Builder(String builderCreationMethod) { + this.builderCreationMethod = builderCreationMethod; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Task wrongCreate() { + return new Task( this, "wrongCreate" ); + } + + public Task build() { + return new Task( this, "build" ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/TooManyBuilderCreationMethodsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/TooManyBuilderCreationMethodsMapper.java new file mode 100644 index 0000000000..a5d7b6d38c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/TooManyBuilderCreationMethodsMapper.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.ap.test.builder.multiple; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builder.multiple.builder.Case; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface TooManyBuilderCreationMethodsMapper { + + TooManyBuilderCreationMethodsMapper INSTANCE = Mappers.getMapper( TooManyBuilderCreationMethodsMapper.class ); + + Case map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/build/Process.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/build/Process.java new file mode 100644 index 0000000000..8e30310c6f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/build/Process.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.ap.test.builder.multiple.build; + +/** + * @author Filip Hrisafov + */ +public class Process { + + private final String builderCreationMethod; + private final String buildMethod; + private String name; + + public Process() { + this.builderCreationMethod = null; + this.buildMethod = "constructor"; + } + + public Process(Builder builder, String buildMethod) { + this.builderCreationMethod = builder.builderCreationMethod; + this.buildMethod = buildMethod; + this.name = builder.name; + } + + public String getBuilderCreationMethod() { + return builderCreationMethod; + } + + public String getBuildMethod() { + return buildMethod; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public static Builder builder() { + return new Builder( "builder" ); + } + + public static class Builder { + + private String name; + private String builderCreationMethod; + + protected Builder(String builderCreationMethod) { + this.builderCreationMethod = builderCreationMethod; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Process wrongCreate() { + return new Process( this, "wrongCreate" ); + } + + public Process create() { + return new Process( this, "create" ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/builder/Case.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/builder/Case.java new file mode 100644 index 0000000000..d1d9805462 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/builder/Case.java @@ -0,0 +1,70 @@ +/* + * 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.test.builder.multiple.builder; + +/** + * @author Filip Hrisafov + */ +public class Case { + + private final String builderCreationMethod; + private final String buildMethod; + private String name; + + public Case() { + this.builderCreationMethod = null; + this.buildMethod = "constructor"; + } + + public Case(Builder builder, String buildMethod) { + this.builderCreationMethod = builder.builderCreationMethod; + this.buildMethod = buildMethod; + this.name = builder.name; + } + + public String getBuilderCreationMethod() { + return builderCreationMethod; + } + + public String getBuildMethod() { + return buildMethod; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public static Builder wrongBuilder() { + return new Builder( "wrongBuilder" ); + } + + public static Builder builder() { + return new Builder( "builder" ); + } + + public static class Builder { + + private String name; + private String builderCreationMethod; + + protected Builder(String builderCreationMethod) { + this.builderCreationMethod = builderCreationMethod; + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Case create() { + return new Case( this, "create" ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/BuilderNestedPropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/BuilderNestedPropertyTest.java new file mode 100644 index 0000000000..94233a373e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/BuilderNestedPropertyTest.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.test.builder.nestedprop.expanding; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that nested property mapping works with an immutable intermediate type. + */ +@WithClasses({ + FlattenedStock.class, + ImmutableExpandedStock.class, + ImmutableArticle.class, + ExpandingMapper.class +}) +public class BuilderNestedPropertyTest { + + @ProcessorTest + public void testNestedImmutablePropertyMapper() { + FlattenedStock stock = new FlattenedStock( "Sock", "Tie", 33 ); + ImmutableExpandedStock expandedTarget = ExpandingMapper.INSTANCE.writeToNestedProperty( stock ); + assertThat( expandedTarget ).isNotNull(); + assertThat( expandedTarget.getCount() ).isEqualTo( 33 ); + assertThat( expandedTarget.getSecond() ).isNull(); + assertThat( expandedTarget.getFirst() ).isNotNull(); + assertThat( expandedTarget.getFirst().getDescription() ).isEqualTo( "Sock" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ExpandingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ExpandingMapper.java new file mode 100644 index 0000000000..9b2196da07 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ExpandingMapper.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.ap.test.builder.nestedprop.expanding; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ExpandingMapper { + + ExpandingMapper INSTANCE = Mappers.getMapper( ExpandingMapper.class ); + + @Mappings({ + @Mapping(target = "articleCount", source = "count"), + @Mapping(target = "first.description", source = "article1"), + @Mapping(target = "second", ignore = true) + }) + ImmutableExpandedStock writeToNestedProperty(FlattenedStock source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/FlattenedStock.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/FlattenedStock.java new file mode 100644 index 0000000000..f7cce1a03f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/FlattenedStock.java @@ -0,0 +1,47 @@ +/* + * 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.test.builder.nestedprop.expanding; + +import static java.util.Objects.requireNonNull; + +public class FlattenedStock { + private String article1; + private String article2; + private int count; + + public FlattenedStock() { + } + + public FlattenedStock(String article1, String article2, int count) { + this.article1 = requireNonNull( article1 ); + this.article2 = requireNonNull( article2 ); + this.count = count; + } + + public String getArticle1() { + return article1; + } + + public void setArticle1(String article1) { + this.article1 = article1; + } + + public String getArticle2() { + return article2; + } + + public void setArticle2(String article2) { + this.article2 = article2; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ImmutableArticle.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ImmutableArticle.java new file mode 100644 index 0000000000..2d86e74d0d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ImmutableArticle.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.test.builder.nestedprop.expanding; + +public class ImmutableArticle { + private final String description; + + ImmutableArticle(ImmutableArticle.Builder builder) { + this.description = builder.description; + } + + public static ImmutableArticle.Builder builder() { + return new ImmutableArticle.Builder(); + } + + public String getDescription() { + return description; + } + + public static class Builder { + private String description; + + public ImmutableArticle build() { + return new ImmutableArticle( this ); + } + + public ImmutableArticle.Builder description(String description) { + this.description = description; + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ImmutableExpandedStock.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ImmutableExpandedStock.java new file mode 100644 index 0000000000..2bfc65f0b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/expanding/ImmutableExpandedStock.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.ap.test.builder.nestedprop.expanding; + +public class ImmutableExpandedStock { + private final int count; + private final ImmutableArticle first; + private final ImmutableArticle second; + + ImmutableExpandedStock(Builder builder) { + this.count = builder.articleCount; + this.first = builder.first; + this.second = builder.second; + } + + public static Builder builder() { + return new Builder(); + } + + public int getCount() { + return count; + } + + public ImmutableArticle getFirst() { + return first; + } + + public ImmutableArticle getSecond() { + return second; + } + + public static class Builder { + private int articleCount; + private ImmutableArticle first; + private ImmutableArticle second; + + public Builder articleCount(int articleCount) { + this.articleCount = articleCount; + return this; + } + + public Builder first(ImmutableArticle first) { + this.first = first; + return this; + } + + public Builder second(ImmutableArticle second) { + this.second = second; + return this; + } + + public ImmutableExpandedStock build() { + return new ImmutableExpandedStock( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/Article.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/Article.java new file mode 100644 index 0000000000..c24cb55b5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/Article.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.ap.test.builder.nestedprop.flattening; + +public class Article { + private String description; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/BuilderNestedPropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/BuilderNestedPropertyTest.java new file mode 100644 index 0000000000..861bf810bf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/BuilderNestedPropertyTest.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.ap.test.builder.nestedprop.flattening; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that nested property mapping works with an immutable intermediate type. + */ +@WithClasses({ + ImmutableFlattenedStock.class, + Stock.class, + Article.class, + FlatteningMapper.class +}) +public class BuilderNestedPropertyTest { + + @ProcessorTest + public void testNestedImmutablePropertyMapper() { + + Stock stock = new Stock(); + Article article1 = new Article(); + article1.setDescription( "shavingfoam" ); + Article article2 = new Article(); + article2.setDescription( "soap" ); + stock.setCount( 2 ); + stock.setFirst( article1 ); + stock.setSecond( article2 ); + + ImmutableFlattenedStock flattenedTarget = FlatteningMapper.INSTANCE.writeToFlatProperty( stock ); + + assertThat( flattenedTarget ).isNotNull(); + assertThat( flattenedTarget.getArticleCount() ).isEqualTo( 2 ); + assertThat( flattenedTarget.getArticle1() ).isEqualTo( "shavingfoam" ); + assertThat( flattenedTarget.getArticle2() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/FlatteningMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/FlatteningMapper.java new file mode 100644 index 0000000000..b3b4123e83 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/FlatteningMapper.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.ap.test.builder.nestedprop.flattening; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface FlatteningMapper { + + FlatteningMapper INSTANCE = Mappers.getMapper( FlatteningMapper.class ); + + @Mappings({ + @Mapping(target = "articleCount", source = "count"), + @Mapping(target = "article1", source = "first.description"), + @Mapping(target = "article2", ignore = true) + }) + ImmutableFlattenedStock writeToFlatProperty(Stock source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/ImmutableFlattenedStock.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/ImmutableFlattenedStock.java new file mode 100644 index 0000000000..bc941fc290 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/ImmutableFlattenedStock.java @@ -0,0 +1,65 @@ +/* + * 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.test.builder.nestedprop.flattening; + +public class ImmutableFlattenedStock { + + private final String article1; + private final String article2; + private final int articleCount; + + public ImmutableFlattenedStock(String article1, String article2, int count) { + this.article1 = article1; + this.article2 = article2; + this.articleCount = count; + } + + public String getArticle1() { + return article1; + } + + public String getArticle2() { + return article2; + } + + public int getArticleCount() { + return articleCount; + } + + public static ImmutableFlattenedStock.Builder builder() { + return new ImmutableFlattenedStock.Builder(); + } + + public static class Builder { + + private String article1; + private String article2; + private int articleCount; + + public Builder() { + } + + public Builder setArticle1(String article1) { + this.article1 = article1; + return this; + } + + public Builder setArticle2(String article2) { + this.article2 = article2; + return this; + } + + public Builder setArticleCount(int articleCount) { + this.articleCount = articleCount; + return this; + } + + public ImmutableFlattenedStock build() { + return new ImmutableFlattenedStock( article1, article2, articleCount ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/Stock.java b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/Stock.java new file mode 100644 index 0000000000..56845d1163 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/nestedprop/flattening/Stock.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.test.builder.nestedprop.flattening; + +public class Stock { + private int count; + private Article first; + private Article second; + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public Article getFirst() { + return first; + } + + public void setFirst(Article first) { + this.first = first; + } + + public Article getSecond() { + return second; + } + + public void setSecond(Article second) { + this.second = second; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/noop/NoOpBuilderProviderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/noop/NoOpBuilderProviderTest.java new file mode 100644 index 0000000000..e90babfdd8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/noop/NoOpBuilderProviderTest.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.test.builder.noop; + +import org.mapstruct.ap.spi.NoOpBuilderProvider; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithServiceImplementation; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey( "1418" ) +@WithServiceImplementation(NoOpBuilderProvider.class) +@WithClasses( { + Person.class, + PersonDto.class, + PersonMapper.class +} ) +public class NoOpBuilderProviderTest { + + @ProcessorTest + public void shouldNotUseBuilder() { + Person person = PersonMapper.INSTANCE.map( new PersonDto( "Filip" ) ); + + assertThat( person ).isNotNull(); + assertThat( person.getName() ).isEqualTo( "Filip" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/noop/Person.java b/processor/src/test/java/org/mapstruct/ap/test/builder/noop/Person.java new file mode 100644 index 0000000000..02e371f773 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/noop/Person.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.test.builder.noop; + +/** + * @author Filip Hrisafov + */ +public class Person { + + private String name; + + public Person() { + + } + + public Person(Builder builder) { + this.name = builder.name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Builder create() { + throw new UnsupportedOperationException( "Creating builder is not supported" ); + } + + public static class Builder { + + private String name; + + public Builder() { + throw new UnsupportedOperationException( "Creating a builder is not supported" ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Person create() { + return new Person(this); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/noop/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/builder/noop/PersonDto.java new file mode 100644 index 0000000000..f739803330 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/noop/PersonDto.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.ap.test.builder.noop; + +/** + * @author Filip Hrisafov + */ +public class PersonDto { + + private final String name; + + public PersonDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/noop/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/noop/PersonMapper.java new file mode 100644 index 0000000000..1aa0072dc3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/noop/PersonMapper.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.ap.test.builder.noop; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface PersonMapper { + + PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); + + Person map(PersonDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleMapper.java new file mode 100644 index 0000000000..94e5101720 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleMapper.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.ap.test.builder.off; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SimpleMapper { + + @BeanMapping( builder = @Builder( disableBuilder = true ) ) + @Mapping(target = "name", source = "fullName") + SimpleNotRealyImmutablePerson toNotRealyImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleMutablePerson.java b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleMutablePerson.java new file mode 100644 index 0000000000..d3cde44d21 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleMutablePerson.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.ap.test.builder.off; + +public class SimpleMutablePerson { + private String fullName; + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutableBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutableBuilderTest.java new file mode 100644 index 0000000000..30cf5ff12d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutableBuilderTest.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.ap.test.builder.off; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1661") +@WithClasses({ + SimpleMutablePerson.class, + SimpleNotRealyImmutablePerson.class +}) +public class SimpleNotRealyImmutableBuilderTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ SimpleMapper.class }) + public void testSimpleImmutableBuilderHappyPath() { + SimpleMapper mapper = Mappers.getMapper( SimpleMapper.class ); + SimpleMutablePerson source = new SimpleMutablePerson(); + source.setFullName( "Bob" ); + + SimpleNotRealyImmutablePerson targetObject = mapper.toNotRealyImmutable( source ); + + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + + } + + @ProcessorTest + @WithClasses({ SimpleWithBuilderMapper.class }) + @ProcessorOption( name = "mapstruct.disableBuilders", value = "true") + public void builderGloballyDisabled() { + SimpleWithBuilderMapper mapper = Mappers.getMapper( SimpleWithBuilderMapper.class ); + SimpleMutablePerson source = new SimpleMutablePerson(); + source.setFullName( "Bob" ); + + SimpleNotRealyImmutablePerson targetObject = mapper.toNotRealyImmutable( source ); + + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutablePerson.java b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutablePerson.java new file mode 100644 index 0000000000..4caba6b016 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleNotRealyImmutablePerson.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.ap.test.builder.off; + +public class SimpleNotRealyImmutablePerson { + + private String name; + + public static Builder builder() { + return new Builder(); + } + + public SimpleNotRealyImmutablePerson() { + } + + SimpleNotRealyImmutablePerson(Builder builder) { + this.name = builder.name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public static class Builder { + + private String name; + + public Builder name(String name) { + throw new IllegalStateException( "name should not be called on builder" ); + } + + public SimpleNotRealyImmutablePerson build() { + return new SimpleNotRealyImmutablePerson( this ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleWithBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleWithBuilderMapper.java new file mode 100644 index 0000000000..6df547db78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/off/SimpleWithBuilderMapper.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.ap.test.builder.off; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SimpleWithBuilderMapper { + + @Mapping(target = "name", source = "fullName") + SimpleNotRealyImmutablePerson toNotRealyImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ImmutableChild.java b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ImmutableChild.java new file mode 100644 index 0000000000..e4d067c704 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ImmutableChild.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.test.builder.parentchild; + +public class ImmutableChild { + + private final String name; + + private ImmutableChild(Builder builder) { + this.name = builder.name; + } + + public static Builder builder() { + return new Builder(); + } + + public String getName() { + return name; + } + + public static class Builder { + private String name; + + public ImmutableChild.Builder name(String name) { + this.name = name; + return this; + } + + public ImmutableChild build() { + return new ImmutableChild( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ImmutableParent.java b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ImmutableParent.java new file mode 100644 index 0000000000..b77860532b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ImmutableParent.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.ap.test.builder.parentchild; + +import java.util.List; + +public class ImmutableParent { + private final int count; + private final List children; + + ImmutableParent(Builder builder) { + this.count = builder.count; + this.children = builder.children; + } + + public static Builder builder() { + return new Builder(); + } + + public int getCount() { + return count; + } + + public List getChildren() { + return children; + } + + public static class Builder { + private List children; + private int count; + + public Builder children(List children) { + this.children = children; + return this; + } + + public ImmutableParent build() { + return new ImmutableParent( this ); + } + + public Builder count(int count) { + this.count = count; + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/MutableChild.java b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/MutableChild.java new file mode 100644 index 0000000000..e1e2b7c71f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/MutableChild.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.ap.test.builder.parentchild; + +public class MutableChild { + + public MutableChild() { + } + + public MutableChild(String childName) { + this.childName = childName; + } + + private String childName; + + public String getChildName() { + return childName; + } + + public void setChildName(String childName) { + this.childName = childName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/MutableParent.java b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/MutableParent.java new file mode 100644 index 0000000000..ef91baef48 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/MutableParent.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.ap.test.builder.parentchild; + +import java.util.List; + +public class MutableParent { + private int count; + private List children; + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildBuilderTest.java new file mode 100644 index 0000000000..9269390640 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildBuilderTest.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.ap.test.builder.parentchild; + +import java.util.ArrayList; + +import org.assertj.core.api.Condition; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + MutableParent.class, + MutableChild.class, + ImmutableChild.class, + ImmutableParent.class, + ParentChildMapper.class +}) +public class ParentChildBuilderTest { + + @ProcessorTest + public void testParentChildBuilderMapper() { + final MutableParent parent = new MutableParent(); + parent.setCount( 4 ); + parent.setChildren( new ArrayList<>() ); + parent.getChildren().add( new MutableChild( "Phineas" ) ); + parent.getChildren().add( new MutableChild( "Ferb" ) ); + + // transform + ImmutableParent immutableParent = ParentChildMapper.INSTANCE.toParent( parent ); + + assertThat( immutableParent.getCount() ).isEqualTo( 4 ); + assertThat( immutableParent.getChildren() ).hasSize( 2 ); + assertThat( immutableParent.getChildren() ) + .hasSize( 2 ) + .areExactly( 1, hasMatchingName( "Phineas" ) ) + .areExactly( 1, hasMatchingName( "Ferb" ) ); + } + + private Condition hasMatchingName(final String name) { + return new Condition<>( "Matching name" ) { + @Override + public boolean matches(ImmutableChild value) { + return name.equals( value.getName() ); + } + }; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildMapper.java new file mode 100644 index 0000000000..179c9f947c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildMapper.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.ap.test.builder.parentchild; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ParentChildMapper { + + ParentChildMapper INSTANCE = Mappers.getMapper( ParentChildMapper.class ); + + ImmutableParent toParent(MutableParent source); + + @Mapping(target = "name", source = "childName") + ImmutableChild toChild(MutableChild source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleMutablePerson.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleMutablePerson.java new file mode 100644 index 0000000000..34fd71ad92 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleMutablePerson.java @@ -0,0 +1,47 @@ +/* + * 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.test.builder.simple; + +import java.util.List; + +public class SimpleMutablePerson { + private String fullName; + private int age; + private String address; + private List children; + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.java new file mode 100644 index 0000000000..18e82ed19c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/ErroneousSimpleBuilderMapper.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.ap.test.builder.simple.innerclass; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface ErroneousSimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "address", ignore = true ), + @Mapping(target = "job", ignore = true ), + @Mapping(target = "city", ignore = true ) + }) + SimpleImmutablePersonWithInnerClassBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.java new file mode 100644 index 0000000000..e2c673401a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleBuilderMapper.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.ap.test.builder.simple.innerclass; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "name", source = "fullName"), + @Mapping(target = "job", constant = "programmer"), + @Mapping(target = "city", expression = "java(\"Bengalore\")") + }) + SimpleImmutablePersonWithInnerClassBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.java new file mode 100644 index 0000000000..0a2c5287bc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutableBuilderThroughInnerClassConstructorTest.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.ap.test.builder.simple.innerclass; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SimpleMutablePerson.class, + SimpleImmutablePersonWithInnerClassBuilder.class +}) +public class SimpleImmutableBuilderThroughInnerClassConstructorTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ SimpleBuilderMapper.class }) + public void testSimpleImmutableBuilderThroughInnerClassConstructorHappyPath() { + SimpleBuilderMapper mapper = Mappers.getMapper( SimpleBuilderMapper.class ); + SimpleMutablePerson source = new SimpleMutablePerson(); + source.setAge( 3 ); + source.setFullName( "Bob" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + source.setAddress( "Plaza 1" ); + + SimpleImmutablePersonWithInnerClassBuilder targetObject = mapper.toImmutable( source ); + + assertThat( targetObject.getAge() ).isEqualTo( 3 ); + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + assertThat( targetObject.getJob() ).isEqualTo( "programmer" ); + assertThat( targetObject.getCity() ).isEqualTo( "Bengalore" ); + assertThat( targetObject.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( targetObject.getChildren() ).contains( "Alice", "Tom" ); + } + + @ProcessorTest + @WithClasses({ ErroneousSimpleBuilderMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSimpleBuilderMapper.class, + line = 22, + message = "Unmapped target property: \"name\".")) + public void testSimpleImmutableBuilderThroughInnerClassConstructorMissingPropertyFailsToCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.java new file mode 100644 index 0000000000..a9bda42d33 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/innerclass/SimpleImmutablePersonWithInnerClassBuilder.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.ap.test.builder.simple.innerclass; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleImmutablePersonWithInnerClassBuilder { + private final String name; + private final int age; + private final String job; + private final String city; + private final String address; + private final List children; + + SimpleImmutablePersonWithInnerClassBuilder(Builder builder) { + this.name = builder.name; + this.age = builder.age; + this.job = builder.job; + this.city = builder.city; + this.address = builder.address; + this.children = new ArrayList<>(builder.children); + } + + public int getAge() { + return age; + } + + public String getName() { + return name; + } + + public String getJob() { + return job; + } + + public String getCity() { + return city; + } + + public String getAddress() { + return address; + } + + public List getChildren() { + return children; + } + + public static class Builder { + private String name; + private int age; + private String job; + private String city; + private String address; + private List children = new ArrayList<>(); + + public Builder age(int age) { + this.age = age; + return this; + } + + public SimpleImmutablePersonWithInnerClassBuilder build() { + return new SimpleImmutablePersonWithInnerClassBuilder( this ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder job(String job) { + this.job = job; + return this; + } + + public Builder city(String city) { + this.city = city; + return this; + } + + public Builder address(String address) { + this.address = address; + return this; + } + + public List getChildren() { + throw new UnsupportedOperationException( "This is just a marker method" ); + } + + public Builder addChild(String child) { + this.children.add( child ); + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.java new file mode 100644 index 0000000000..6fc51ace3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/ErroneousSimpleBuilderMapper.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.ap.test.builder.simple.staticfactorymethod; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) +public interface ErroneousSimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "address", ignore = true ), + @Mapping(target = "job", ignore = true ), + @Mapping(target = "city", ignore = true ) + }) + SimpleImmutablePersonWithStaticFactoryMethodBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.java new file mode 100644 index 0000000000..4bd200bc7c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleBuilderMapper.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.ap.test.builder.simple.staticfactorymethod; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; + +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SimpleBuilderMapper { + + @Mappings({ + @Mapping(target = "name", source = "fullName"), + @Mapping(target = "job", constant = "programmer"), + @Mapping(target = "city", expression = "java(\"Bengalore\")") + }) + SimpleImmutablePersonWithStaticFactoryMethodBuilder toImmutable(SimpleMutablePerson source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.java new file mode 100644 index 0000000000..90fac6061e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutableBuilderThroughStaticFactoryMethodTest.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.ap.test.builder.simple.staticfactorymethod; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.builder.simple.SimpleMutablePerson; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.mapstruct.factory.Mappers; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SimpleMutablePerson.class, + SimpleImmutablePersonWithStaticFactoryMethodBuilder.class +}) +public class SimpleImmutableBuilderThroughStaticFactoryMethodTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ SimpleBuilderMapper.class }) + public void testSimpleImmutableBuilderThroughStaticFactoryMethodHappyPath() { + SimpleBuilderMapper mapper = Mappers.getMapper( SimpleBuilderMapper.class ); + SimpleMutablePerson source = new SimpleMutablePerson(); + source.setAge( 3 ); + source.setFullName( "Bob" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + source.setAddress( "Plaza 1" ); + + SimpleImmutablePersonWithStaticFactoryMethodBuilder targetObject = mapper.toImmutable( source ); + + assertThat( targetObject.getAge() ).isEqualTo( 3 ); + assertThat( targetObject.getName() ).isEqualTo( "Bob" ); + assertThat( targetObject.getJob() ).isEqualTo( "programmer" ); + assertThat( targetObject.getCity() ).isEqualTo( "Bengalore" ); + assertThat( targetObject.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( targetObject.getChildren() ).contains( "Alice", "Tom" ); + } + + @ProcessorTest + @WithClasses({ ErroneousSimpleBuilderMapper.class }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSimpleBuilderMapper.class, + line = 22, + message = "Unmapped target property: \"name\".")) + public void testSimpleImmutableBuilderThroughStaticFactoryMethodMissingPropertyFailsToCompile() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java new file mode 100644 index 0000000000..c3dc590210 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/staticfactorymethod/SimpleImmutablePersonWithStaticFactoryMethodBuilder.java @@ -0,0 +1,105 @@ +/* + * 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.test.builder.simple.staticfactorymethod; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleImmutablePersonWithStaticFactoryMethodBuilder { + private final String name; + private final int age; + private final String job; + private final String city; + private final String address; + private final List children; + + SimpleImmutablePersonWithStaticFactoryMethodBuilder(Builder builder) { + this.name = builder.name; + this.age = builder.age; + this.job = builder.job; + this.city = builder.city; + this.address = builder.address; + this.children = new ArrayList<>( builder.children ); + } + + public static Builder builder() { + return new Builder(); + } + + public int getAge() { + return age; + } + + public String getName() { + return name; + } + + public String getJob() { + return job; + } + + public String getCity() { + return city; + } + + public String getAddress() { + return address; + } + + public List getChildren() { + return children; + } + + public static class Builder { + private String name; + private int age; + private String job; + private String city; + private String address; + private List children = new ArrayList<>(); + + private Builder() { + } + + public Builder age(int age) { + this.age = age; + return this; + } + + public SimpleImmutablePersonWithStaticFactoryMethodBuilder build() { + return new SimpleImmutablePersonWithStaticFactoryMethodBuilder( this ); + } + + public Builder name(String name) { + this.name = name; + return this; + } + + public Builder job(String job) { + this.job = job; + return this; + } + + public Builder city(String city) { + this.city = city; + return this; + } + + public Builder address(String address) { + this.address = address; + return this; + } + + public List getChildren() { + throw new UnsupportedOperationException( "This is just a marker method" ); + } + + public Builder addChild(String child) { + this.children.add( child ); + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java index 35b0cc7e1f..c8ee922af9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/BuiltInTest.java @@ -1,28 +1,15 @@ -/** - * Copyright 2012-2015 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.builtin; -import org.mapstruct.ap.test.builtin.mapper.SourceTargetWithSqlDateMapper; - -import java.text.DateFormat; +import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -30,39 +17,41 @@ import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; -import java.util.TimeZone; - import javax.xml.bind.JAXBElement; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.test.builtin._target.IterableTarget; import org.mapstruct.ap.test.builtin._target.MapTarget; -import org.mapstruct.ap.test.builtin._target.TargetWithDate; -import org.mapstruct.ap.test.builtin._target.TargetWithSqlDate; +import org.mapstruct.ap.test.builtin.bean.BigDecimalProperty; import org.mapstruct.ap.test.builtin.bean.CalendarProperty; import org.mapstruct.ap.test.builtin.bean.DateProperty; +import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementListProperty; +import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementProperty; import org.mapstruct.ap.test.builtin.bean.JaxbElementListProperty; import org.mapstruct.ap.test.builtin.bean.JaxbElementProperty; +import org.mapstruct.ap.test.builtin.bean.SomeType; +import org.mapstruct.ap.test.builtin.bean.SomeTypeProperty; import org.mapstruct.ap.test.builtin.bean.StringListProperty; import org.mapstruct.ap.test.builtin.bean.StringProperty; import org.mapstruct.ap.test.builtin.bean.XmlGregorianCalendarProperty; +import org.mapstruct.ap.test.builtin.java8time.bean.ZonedDateTimeProperty; +import org.mapstruct.ap.test.builtin.java8time.mapper.CalendarToZonedDateTimeMapper; +import org.mapstruct.ap.test.builtin.java8time.mapper.ZonedDateTimeToCalendarMapper; import org.mapstruct.ap.test.builtin.mapper.CalendarToDateMapper; import org.mapstruct.ap.test.builtin.mapper.CalendarToStringMapper; import org.mapstruct.ap.test.builtin.mapper.CalendarToXmlGregCalMapper; import org.mapstruct.ap.test.builtin.mapper.DateToCalendarMapper; import org.mapstruct.ap.test.builtin.mapper.DateToXmlGregCalMapper; import org.mapstruct.ap.test.builtin.mapper.IterableSourceTargetMapper; +import org.mapstruct.ap.test.builtin.mapper.JakartaJaxbListMapper; +import org.mapstruct.ap.test.builtin.mapper.JakartaJaxbMapper; import org.mapstruct.ap.test.builtin.mapper.JaxbListMapper; import org.mapstruct.ap.test.builtin.mapper.JaxbMapper; import org.mapstruct.ap.test.builtin.mapper.MapSourceTargetMapper; -import org.mapstruct.ap.test.builtin.mapper.SourceTargetWithDateMapper; import org.mapstruct.ap.test.builtin.mapper.StringToCalendarMapper; import org.mapstruct.ap.test.builtin.mapper.StringToXmlGregCalMapper; import org.mapstruct.ap.test.builtin.mapper.XmlGregCalToCalendarMapper; @@ -70,280 +59,444 @@ import org.mapstruct.ap.test.builtin.mapper.XmlGregCalToStringMapper; import org.mapstruct.ap.test.builtin.source.IterableSource; import org.mapstruct.ap.test.builtin.source.MapSource; -import org.mapstruct.ap.test.builtin.source.SourceWithDate; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; -import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; -import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.WithJakartaJaxb; +import org.mapstruct.ap.testutil.WithJavaxJaxb; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Test for the generation of built-in mapping methods. * * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) +@WithClasses({ + IterableTarget.class, + MapTarget.class, + CalendarProperty.class, + DateProperty.class, + StringListProperty.class, + StringProperty.class, + BigDecimalProperty.class, + SomeTypeProperty.class, + SomeType.class, + XmlGregorianCalendarProperty.class, + ZonedDateTimeProperty.class, + IterableSource.class, +}) +@DefaultTimeZone("Europe/Berlin") public class BuiltInTest { - private static TimeZone originalTimeZone; + @ProcessorTest + @WithClasses( { + JaxbMapper.class, + JaxbElementProperty.class, + } ) + @WithJavaxJaxb + public void shouldApplyBuiltInOnJAXBElement() { + JaxbElementProperty source = new JaxbElementProperty(); + source.setProp( createJaxb( "TEST" ) ); + source.publicProp = createJaxb( "PUBLIC TEST" ); - @BeforeClass - public static void setDefaultTimeZoneToCet() { - originalTimeZone = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( "Europe/Berlin" ) ); + StringProperty target = JaxbMapper.INSTANCE.map( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( "TEST" ); + assertThat( target.publicProp ).isEqualTo( "PUBLIC TEST" ); } - @AfterClass - public static void restoreOriginalTimeZone() { - TimeZone.setDefault( originalTimeZone ); + @ProcessorTest + @WithClasses( { + JakartaJaxbMapper.class, + JakartaJaxbElementProperty.class, + } ) + @WithJakartaJaxb + public void shouldApplyBuiltInOnJakartaJaxbElement() { + JakartaJaxbElementProperty source = new JakartaJaxbElementProperty(); + source.setProp( createJakartaJaxb( "TEST" ) ); + source.publicProp = createJakartaJaxb( "PUBLIC TEST" ); + + StringProperty target = JakartaJaxbMapper.INSTANCE.map( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( "TEST" ); + assertThat( target.publicProp ).isEqualTo( "PUBLIC TEST" ); } - @Test - @WithClasses({ JaxbElementProperty.class, StringProperty.class, JaxbMapper.class }) - public void shoulApplyBuiltInOnJAXBElement() throws ParseException, DatatypeConfigurationException { - + @ProcessorTest + @WithClasses( { + JaxbMapper.class, + JaxbElementProperty.class, + } ) + @WithJavaxJaxb + @IssueKey( "1698" ) + public void shouldApplyBuiltInOnJAXBElementExtra() { JaxbElementProperty source = new JaxbElementProperty(); - source.setProp( createJaxb( "TEST" ) ); + source.setProp( createJaxb( "5" ) ); + source.publicProp = createJaxb( "5" ); - StringProperty target = JaxbMapper.INSTANCE.map( source ); + BigDecimalProperty target = JaxbMapper.INSTANCE.mapBD( source ); assertThat( target ).isNotNull(); - assertThat( target.getProp() ).isEqualTo( "TEST" ); + assertThat( target.getProp() ).isEqualTo( new BigDecimal( "5" ) ); + assertThat( target.publicProp ).isEqualTo( new BigDecimal( "5" ) ); + + JaxbElementProperty source2 = new JaxbElementProperty(); + source2.setProp( createJaxb( "5" ) ); + source2.publicProp = createJaxb( "5" ); + + SomeTypeProperty target2 = JaxbMapper.INSTANCE.mapSomeType( source2 ); + assertThat( target2 ).isNotNull(); + assertThat( target2.publicProp ).isNotNull(); + assertThat( target2.getProp() ).isNotNull(); + } + + @ProcessorTest + @WithClasses( { + JakartaJaxbMapper.class, + JakartaJaxbElementProperty.class, + } ) + @WithJakartaJaxb + @IssueKey( "1698" ) + public void shouldApplyBuiltInOnJakartaJAXBElementExtra() { + JakartaJaxbElementProperty source = new JakartaJaxbElementProperty(); + source.setProp( createJakartaJaxb( "5" ) ); + source.publicProp = createJakartaJaxb( "5" ); + + BigDecimalProperty target = JakartaJaxbMapper.INSTANCE.mapBD( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( new BigDecimal( "5" ) ); + assertThat( target.publicProp ).isEqualTo( new BigDecimal( "5" ) ); + + JakartaJaxbElementProperty source2 = new JakartaJaxbElementProperty(); + source2.setProp( createJakartaJaxb( "5" ) ); + source2.publicProp = createJakartaJaxb( "5" ); + + SomeTypeProperty target2 = JakartaJaxbMapper.INSTANCE.mapSomeType( source2 ); + assertThat( target2 ).isNotNull(); + assertThat( target2.publicProp ).isNotNull(); + assertThat( target2.getProp() ).isNotNull(); } - @Test + @ProcessorTest + @WithClasses( { + JaxbListMapper.class, + JaxbElementListProperty.class, + } ) + @WithJavaxJaxb @IssueKey( "141" ) - @WithClasses({ JaxbElementListProperty.class, StringListProperty.class, JaxbListMapper.class }) - public void shouldApplyBuiltInOnJAXBElementList() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnJAXBElementList() { JaxbElementListProperty source = new JaxbElementListProperty(); source.setProp( createJaxbList( "TEST2" ) ); + source.publicProp = createJaxbList( "PUBLIC TEST2" ); StringListProperty target = JaxbListMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp().get( 0 ) ).isEqualTo( "TEST2" ); + assertThat( target.publicProp.get( 0 ) ).isEqualTo( "PUBLIC TEST2" ); } - @Test - @WithClasses({ DateProperty.class, XmlGregorianCalendarProperty.class, DateToXmlGregCalMapper.class }) - public void shouldApplyBuiltInOnDateToXmlGregCal() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( { + JakartaJaxbListMapper.class, + JakartaJaxbElementListProperty.class, + } ) + @WithJakartaJaxb + @IssueKey( "141" ) + public void shouldApplyBuiltInOnJakartaJAXBElementList() { + JakartaJaxbElementListProperty source = new JakartaJaxbElementListProperty(); + source.setProp( createJakartaJaxbList( "TEST2" ) ); + source.publicProp = createJakartaJaxbList( "PUBLIC TEST2" ); + + StringListProperty target = JakartaJaxbListMapper.INSTANCE.map( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp().get( 0 ) ).isEqualTo( "TEST2" ); + assertThat( target.publicProp.get( 0 ) ).isEqualTo( "PUBLIC TEST2" ); + } + + @ProcessorTest + @WithClasses( DateToXmlGregCalMapper.class ) + public void shouldApplyBuiltInOnDateToXmlGregCal() throws ParseException { DateProperty source = new DateProperty(); source.setProp( createDate( "31-08-1982 10:20:56" ) ); + source.publicProp = createDate( "31-08-2016 10:20:56" ); XmlGregorianCalendarProperty target = DateToXmlGregCalMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); assertThat( target.getProp().toString() ).isEqualTo( "1982-08-31T10:20:56.000+02:00" ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp.toString() ).isEqualTo( "2016-08-31T10:20:56.000+02:00" ); } - @Test - @WithClasses({ DateProperty.class, XmlGregorianCalendarProperty.class, XmlGregCalToDateMapper.class }) - public void shouldApplyBuiltInOnXmlGregCalToDate() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( XmlGregCalToDateMapper.class ) + public void shouldApplyBuiltInOnXmlGregCalToDate() throws DatatypeConfigurationException { XmlGregorianCalendarProperty source = new XmlGregorianCalendarProperty(); source.setProp( createXmlCal( 1999, 3, 2, 60 ) ); + source.publicProp = createXmlCal( 2016, 3, 2, 60 ); DateProperty target = XmlGregCalToDateMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); assertThat( target.getProp().toString() ).isEqualTo( "Tue Mar 02 00:00:00 CET 1999" ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp.toString() ).isEqualTo( "Wed Mar 02 00:00:00 CET 2016" ); } - @Test - @WithClasses({ StringProperty.class, XmlGregorianCalendarProperty.class, StringToXmlGregCalMapper.class }) - public void shouldApplyBuiltInStringToXmlGregCal() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( StringToXmlGregCalMapper.class ) + public void shouldApplyBuiltInStringToXmlGregCal() { StringProperty source = new StringProperty(); source.setProp( "05.07.1999" ); + source.publicProp = "05.07.2016"; XmlGregorianCalendarProperty target = StringToXmlGregCalMapper.INSTANCE.mapAndFormat( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); assertThat( target.getProp().toString() ).isEqualTo( "1999-07-05T00:00:00.000+02:00" ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp.toString() ).isEqualTo( "2016-07-05T00:00:00.000+02:00" ); // direct,via lexical representation source.setProp( "2000-03-04T23:00:00+03:00" ); + source.publicProp = "2016-03-04T23:00:00+03:00"; target = StringToXmlGregCalMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); assertThat( target.getProp().toString() ).isEqualTo( "2000-03-04T23:00:00+03:00" ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp.toString() ).isEqualTo( "2016-03-04T23:00:00+03:00" ); // null string source.setProp( null ); + source.publicProp = null; target = StringToXmlGregCalMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNull(); + assertThat( target.publicProp ).isNull(); } - @Test - @WithClasses({ StringProperty.class, XmlGregorianCalendarProperty.class, XmlGregCalToStringMapper.class }) - public void shouldApplyBuiltInXmlGregCalToString() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( XmlGregCalToStringMapper.class ) + public void shouldApplyBuiltInXmlGregCalToString() throws DatatypeConfigurationException { XmlGregorianCalendarProperty source = new XmlGregorianCalendarProperty(); source.setProp( createXmlCal( 1999, 3, 2, 60 ) ); + source.publicProp = createXmlCal( 2016, 3, 2, 60 ); StringProperty target = XmlGregCalToStringMapper.INSTANCE.mapAndFormat( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); assertThat( target.getProp() ).isEqualTo( "02.03.1999" ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp ).isEqualTo( "02.03.2016" ); source.setProp( createXmlCal( 1999, 3, 2, 60 ) ); + source.publicProp = createXmlCal( 2016, 3, 2, 60 ); target = XmlGregCalToStringMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); assertThat( target.getProp() ).isEqualTo( "1999-03-02+01:00" ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp ).isEqualTo( "2016-03-02+01:00" ); } - @Test - @WithClasses({ CalendarProperty.class, XmlGregorianCalendarProperty.class, CalendarToXmlGregCalMapper.class }) - public void shouldApplyBuiltInOnCalendarToXmlGregCal() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( CalendarToXmlGregCalMapper.class ) + public void shouldApplyBuiltInOnCalendarToXmlGregCal() throws ParseException { CalendarProperty source = new CalendarProperty(); source.setProp( createCalendar( "02.03.1999" ) ); + source.publicProp = createCalendar( "02.03.2016" ); XmlGregorianCalendarProperty target = CalendarToXmlGregCalMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); assertThat( target.getProp().toString() ).isEqualTo( "1999-03-02T00:00:00.000+01:00" ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp.toString() ).isEqualTo( "2016-03-02T00:00:00.000+01:00" ); } - @Test - @WithClasses({ CalendarProperty.class, XmlGregorianCalendarProperty.class, XmlGregCalToCalendarMapper.class }) - public void shouldApplyBuiltInOnXmlGregCalToCalendar() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( XmlGregCalToCalendarMapper.class ) + public void shouldApplyBuiltInOnXmlGregCalToCalendar() throws DatatypeConfigurationException { XmlGregorianCalendarProperty source = new XmlGregorianCalendarProperty(); source.setProp( createXmlCal( 1999, 3, 2, 60 ) ); + source.publicProp = createXmlCal( 2016, 3, 2, 60 ); CalendarProperty target = XmlGregCalToCalendarMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); assertThat( target.getProp().getTimeInMillis() ).isEqualTo( 920329200000L ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp.getTimeInMillis() ).isEqualTo( 1456873200000L ); + assertThat( target.publicProp.getTimeInMillis() ).isEqualTo( 1456873200000L ); } - @Test - @WithClasses({ DateProperty.class, CalendarProperty.class, CalendarToDateMapper.class }) - public void shouldApplyBuiltInOnCalendarToDate() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( CalendarToDateMapper.class ) + public void shouldApplyBuiltInOnCalendarToDate() throws ParseException { CalendarProperty source = new CalendarProperty(); source.setProp( createCalendar( "02.03.1999" ) ); + source.publicProp = createCalendar( "02.03.2016" ); DateProperty target = CalendarToDateMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); - assertThat( target.getProp()).isEqualTo( createCalendar( "02.03.1999" ).getTime()); + assertThat( target.getProp() ).isEqualTo( createCalendar( "02.03.1999" ).getTime() ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp ).isEqualTo( createCalendar( "02.03.2016" ).getTime() ); } - @Test - @WithClasses({ CalendarProperty.class, DateProperty.class, DateToCalendarMapper.class }) - public void shouldApplyBuiltInOnDateToCalendar() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( DateToCalendarMapper.class ) + public void shouldApplyBuiltInOnDateToCalendar() throws ParseException { DateProperty source = new DateProperty(); source.setProp( new SimpleDateFormat( "dd.MM.yyyy" ).parse( "02.03.1999" ) ); + source.publicProp = new SimpleDateFormat( "dd.MM.yyyy" ).parse( "02.03.2016" ); CalendarProperty target = DateToCalendarMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); - assertThat( target.getProp()).isEqualTo( createCalendar( "02.03.1999" )); + assertThat( target.getProp() ).isEqualTo( createCalendar( "02.03.1999" ) ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp ).isEqualTo( createCalendar( "02.03.2016" ) ); } - @Test - @WithClasses({ StringProperty.class, CalendarProperty.class, CalendarToStringMapper.class }) - public void shouldApplyBuiltInOnCalendarToString() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( CalendarToStringMapper.class ) + public void shouldApplyBuiltInOnCalendarToString() throws ParseException { CalendarProperty source = new CalendarProperty(); source.setProp( createCalendar( "02.03.1999" ) ); + source.publicProp = createCalendar( "02.03.2016" ); StringProperty target = CalendarToStringMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); - assertThat( target.getProp()).isEqualTo( "02.03.1999" ); + assertThat( target.getProp() ).isEqualTo( "02.03.1999" ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp ).isEqualTo( "02.03.2016" ); } - @Test - @WithClasses({ CalendarProperty.class, StringProperty.class, StringToCalendarMapper.class }) - public void shouldApplyBuiltInOnStringToCalendar() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( StringToCalendarMapper.class ) + public void shouldApplyBuiltInOnStringToCalendar() throws ParseException { StringProperty source = new StringProperty(); source.setProp( "02.03.1999" ); + source.publicProp = "02.03.2016"; CalendarProperty target = StringToCalendarMapper.INSTANCE.map( source ); assertThat( target ).isNotNull(); assertThat( target.getProp() ).isNotNull(); - assertThat( target.getProp()).isEqualTo( createCalendar( "02.03.1999" ) ); + assertThat( target.getProp() ).isEqualTo( createCalendar( "02.03.1999" ) ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp ).isEqualTo( createCalendar( "02.03.2016" ) ); } - @Test - @WithClasses({ IterableSource.class, IterableTarget.class, IterableSourceTargetMapper.class }) - public void shouldApplyBuiltInOnIterable() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( IterableSourceTargetMapper.class ) + public void shouldApplyBuiltInOnIterable() throws DatatypeConfigurationException { IterableSource source = new IterableSource(); - source.setDates( Arrays.asList( new XMLGregorianCalendar[] { createXmlCal( 1999, 3, 2, 60 ) } ) ); + source.setDates( Arrays.asList( createXmlCal( 1999, 3, 2, 60 ) ) ); + source.publicDates = Arrays.asList( createXmlCal( 2016, 3, 2, 60 ) ); IterableTarget target = IterableSourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getDates() ).containsExactly( "02.03.1999" ); + assertThat( target.publicDates ).containsExactly( "02.03.2016" ); } - @Test - @WithClasses({ MapSource.class, MapTarget.class, MapSourceTargetMapper.class }) - public void shouldApplyBuiltInOnMap() throws ParseException, DatatypeConfigurationException { + @ProcessorTest + @WithClasses( { + MapSourceTargetMapper.class, + MapSource.class, + } ) + @WithJavaxJaxb + public void shouldApplyBuiltInOnMap() throws DatatypeConfigurationException { MapSource source = new MapSource(); - source.setExample( new HashMap, XMLGregorianCalendar>() ); + source.setExample( new HashMap<>() ); source.getExample().put( createJaxb( "TEST" ), createXmlCal( 1999, 3, 2, 60 ) ); + source.publicExample = new HashMap<>(); + source.publicExample.put( createJaxb( "TEST" ), createXmlCal( 2016, 3, 2, 60 ) ); MapTarget target = MapSourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getExample().get( "TEST" ) ).isEqualTo( "1999-03-02+01:00" ); + assertThat( target.publicExample.get( "TEST" ) ).isEqualTo( "2016-03-02+01:00" ); } - @Test - @IssueKey( "248" ) - @WithClasses( { SourceWithDate.class, TargetWithDate.class, SourceTargetWithDateMapper.class } ) - public void dateToXmlGregorianCalenderHasCorrectImports() { - assertThat( SourceTargetWithDateMapper.INSTANCE.toTargetWithDate( null ) ).isNull(); + @ProcessorTest + @WithClasses( CalendarToZonedDateTimeMapper.class ) + public void shouldApplyBuiltInOnCalendarToZonedDateTime() throws ParseException { + assertThat( CalendarToZonedDateTimeMapper.INSTANCE.map( null ) ).isNull(); + + CalendarProperty source = new CalendarProperty(); + source.setProp( createCalendar( "02.03.1999" ) ); + source.publicProp = createCalendar( "02.03.2016" ); - TargetWithDate targetWithDate = SourceTargetWithDateMapper.INSTANCE.toTargetWithDate( new SourceWithDate() ); - assertThat( targetWithDate ).isNotNull(); - assertThat( targetWithDate.getDate() ).isNull(); + ZonedDateTimeProperty target = CalendarToZonedDateTimeMapper.INSTANCE.map( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( ZonedDateTime.of( 1999, 3, 2, 0, 0, 0, 0, ZoneId.systemDefault() ) ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp ).isEqualTo( ZonedDateTime.of( 2016, 3, 2, 0, 0, 0, 0, ZoneId.systemDefault() ) ); } - @Test - @IssueKey( "277" ) - @WithClasses( { SourceWithDate.class, TargetWithSqlDate.class, SourceTargetWithSqlDateMapper.class } ) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic( type = SourceTargetWithSqlDateMapper.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 35, - messageRegExp = "Can't map property \"java\\.util\\.Date date\" to " - + "\"java\\.sql\\.Date date\"" ) - } - ) - public void shouldNotMapJavaUtilDateToJavaSqlDate() { + @ProcessorTest + @WithClasses( ZonedDateTimeToCalendarMapper.class ) + public void shouldApplyBuiltInOnZonedDateTimeToCalendar() throws ParseException { + assertThat( ZonedDateTimeToCalendarMapper.INSTANCE.map( null ) ).isNull(); + + ZonedDateTimeProperty source = new ZonedDateTimeProperty(); + source.setProp( ZonedDateTime.of( 1999, 3, 2, 0, 0, 0, 0, ZoneId.systemDefault() ) ); + source.publicProp = ZonedDateTime.of( 2016, 3, 2, 0, 0, 0, 0, ZoneId.systemDefault() ); + + CalendarProperty target = ZonedDateTimeToCalendarMapper.INSTANCE.map( source ); + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( createCalendar( "02.03.1999" ) ); + assertThat( target.publicProp ).isNotNull(); + assertThat( target.publicProp ).isEqualTo( createCalendar( "02.03.2016" ) ); } private JAXBElement createJaxb(String test) { - return new JAXBElement( new QName( "www.mapstruct.org", "test" ), String.class, test ); + return new JAXBElement<>( new QName( "www.mapstruct.org", "test" ), String.class, test ); + } + + private jakarta.xml.bind.JAXBElement createJakartaJaxb(String test) { + return new jakarta.xml.bind.JAXBElement<>( new QName( "www.mapstruct.org", "test" ), String.class, test ); } private List> createJaxbList(String test) { - List> result = new ArrayList>(); + List> result = new ArrayList<>(); result.add( createJaxb( test ) ); return result; } + private List> createJakartaJaxbList(String test) { + List> result = new ArrayList<>(); + result.add( createJakartaJaxb( test ) ); + return result; + } + private Date createDate(String date) throws ParseException { SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); return sdf.parse( date ); @@ -360,10 +513,4 @@ private XMLGregorianCalendar createXmlCal(int year, int month, int day, int tz) throws DatatypeConfigurationException { return DatatypeFactory.newInstance().newXMLGregorianCalendarDate( year, month, day, tz ); } - - private String createLocaleDate(String date) throws ParseException { - Date d = createDate( date ); - DateFormat df = SimpleDateFormat.getInstance(); - return df.format( d ); - } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/DatatypeFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/DatatypeFactoryTest.java new file mode 100644 index 0000000000..1db8d3abcf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/DatatypeFactoryTest.java @@ -0,0 +1,68 @@ +/* + * 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.test.builtin; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.test.builtin.bean.CalendarProperty; +import org.mapstruct.ap.test.builtin.bean.DatatypeFactory; +import org.mapstruct.ap.test.builtin.bean.DateProperty; +import org.mapstruct.ap.test.builtin.bean.XmlGregorianCalendarFactorizedProperty; +import org.mapstruct.ap.test.builtin.mapper.ToXmlGregCalMapper; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses( { + DatatypeFactory.class, + XmlGregorianCalendarFactorizedProperty.class, + ToXmlGregCalMapper.class, + CalendarProperty.class, + DateProperty.class + +} ) +@DefaultTimeZone("Europe/Berlin") +public class DatatypeFactoryTest { + + @ProcessorTest + public void testNoConflictsWithOwnDatatypeFactory() throws ParseException { + + DateProperty source1 = new DateProperty(); + source1.setProp( createDate( "31-08-1982 10:20:56" ) ); + + CalendarProperty source2 = new CalendarProperty(); + source2.setProp( createCalendar( "02.03.1999" ) ); + + XmlGregorianCalendarFactorizedProperty target1 = ToXmlGregCalMapper.INSTANCE.map( source1 ); + assertThat( target1 ).isNotNull(); + assertThat( target1.getProp() ).isNotNull(); + assertThat( target1.getProp().toString() ).isEqualTo( "1982-08-31T10:20:56.000+02:00" ); + + XmlGregorianCalendarFactorizedProperty target2 = ToXmlGregCalMapper.INSTANCE.map( source2 ); + assertThat( target2 ).isNotNull(); + assertThat( target2.getProp() ).isNotNull(); + assertThat( target2.getProp().toString() ).isEqualTo( "1999-03-02T00:00:00.000+01:00" ); + + } + + private Date createDate(String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); + return sdf.parse( date ); + } + + private Calendar createCalendar(String cal) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat( "dd.MM.yyyy" ); + GregorianCalendar gcal = new GregorianCalendar(); + gcal.setTime( sdf.parse( cal ) ); + return gcal; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/IterableTarget.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/IterableTarget.java index 974d7bf7e5..bf43900e6b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/IterableTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/IterableTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.builtin._target; @@ -26,6 +13,10 @@ */ public class IterableTarget { + // CHECKSTYLE:OFF + public List publicDates; + // CHECKSTYLE:ON + private List dates; public List getDates() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/MapTarget.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/MapTarget.java index 448d63e46c..b280ddc61d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/MapTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/MapTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.builtin._target; @@ -26,6 +13,10 @@ */ public class MapTarget { + // CHECKSTYLE:OFF + public Map publicExample; + // CHECKSTYLE:ON + private Map example; public Map getExample() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/TargetWithDate.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/TargetWithDate.java deleted file mode 100644 index bd00553b96..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/TargetWithDate.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.builtin._target; - -import javax.xml.datatype.XMLGregorianCalendar; - -/** - * @author Andreas Gudian - * - */ -public class TargetWithDate { - private XMLGregorianCalendar date; - - public XMLGregorianCalendar getDate() { - return date; - } - - public void setDate(XMLGregorianCalendar date) { - this.date = date; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/TargetWithSqlDate.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/TargetWithSqlDate.java deleted file mode 100644 index 158d4fc0b7..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/_target/TargetWithSqlDate.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.builtin._target; - -import java.sql.Date; - -/** - * - * @author Sjaak Derksen - */ -public class TargetWithSqlDate { - - private Date date; - - public Date getDate() { - return date; - } - - public void setDate( Date date ) { - this.date = date; - } - - -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/BigDecimalProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/BigDecimalProperty.java new file mode 100644 index 0000000000..85e92f7c4d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/BigDecimalProperty.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.ap.test.builtin.bean; + +import java.math.BigDecimal; + +public class BigDecimalProperty { + + // CHECKSTYLE:OFF + public BigDecimal publicProp; + // CHECKSTYLE:ON + + private BigDecimal prop; + + public BigDecimal getProp() { + return prop; + } + + public void setProp( BigDecimal prop ) { + this.prop = prop; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/CalendarProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/CalendarProperty.java index 16dc11b660..8ff72ea59c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/CalendarProperty.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/CalendarProperty.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.bean; @@ -22,6 +9,10 @@ public class CalendarProperty { + // CHECKSTYLE:OFF + public Calendar publicProp; + // CHECKSTYLE:ON + private Calendar prop; public Calendar getProp() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/DatatypeFactory.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/DatatypeFactory.java new file mode 100644 index 0000000000..648905677c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/DatatypeFactory.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.ap.test.builtin.bean; + +public class DatatypeFactory { + + public XmlGregorianCalendarFactorizedProperty create() { + return new XmlGregorianCalendarFactorizedProperty( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/DateProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/DateProperty.java index b26ea158fc..8ace868dad 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/DateProperty.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/DateProperty.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.bean; @@ -22,6 +9,10 @@ public class DateProperty { + // CHECKSTYLE:OFF + public Date publicProp; + // CHECKSTYLE:ON + private Date prop; public Date getProp() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementListProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementListProperty.java new file mode 100644 index 0000000000..f6ab537253 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementListProperty.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.ap.test.builtin.bean; + +import java.util.List; + +import jakarta.xml.bind.JAXBElement; + +public class JakartaJaxbElementListProperty { + + // CHECKSTYLE:OFF + public List> publicProp; + // CHECKSTYLE:ON + + private List> prop; + + public List> getProp() { + return prop; + } + + public void setProp( List> prop ) { + this.prop = prop; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementProperty.java new file mode 100644 index 0000000000..0336afe814 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JakartaJaxbElementProperty.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.ap.test.builtin.bean; + +import jakarta.xml.bind.JAXBElement; + +public class JakartaJaxbElementProperty { + + // CHECKSTYLE:OFF + public JAXBElement publicProp; + // CHECKSTYLE:ON + + private JAXBElement prop; + + public JAXBElement getProp() { + return prop; + } + + public void setProp( JAXBElement prop ) { + this.prop = prop; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JaxbElementListProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JaxbElementListProperty.java index c251bb34ac..f69655b618 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JaxbElementListProperty.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JaxbElementListProperty.java @@ -1,28 +1,19 @@ -/** - * Copyright 2012-2015 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.builtin.bean; -import javax.xml.bind.JAXBElement; import java.util.List; +import javax.xml.bind.JAXBElement; public class JaxbElementListProperty { + // CHECKSTYLE:OFF + public List> publicProp; + // CHECKSTYLE:ON + private List> prop; public List> getProp() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JaxbElementProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JaxbElementProperty.java index 0d1dc683d2..c981ab613c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JaxbElementProperty.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/JaxbElementProperty.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.bean; @@ -22,8 +9,11 @@ public class JaxbElementProperty { - private JAXBElement prop; + // CHECKSTYLE:OFF + public JAXBElement publicProp; + // CHECKSTYLE:ON + private JAXBElement prop; public JAXBElement getProp() { return prop; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/SomeType.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/SomeType.java new file mode 100644 index 0000000000..9ba2a38166 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/SomeType.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.ap.test.builtin.bean; + +public class SomeType { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/SomeTypeProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/SomeTypeProperty.java new file mode 100644 index 0000000000..fa40da83a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/SomeTypeProperty.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.ap.test.builtin.bean; + +public class SomeTypeProperty { + + // CHECKSTYLE:OFF + public SomeType publicProp; + // CHECKSTYLE:ON + + private SomeType prop; + + public SomeType getProp() { + return prop; + } + + public void setProp( SomeType prop ) { + this.prop = prop; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/StringListProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/StringListProperty.java index aa000d560a..4cc073ccd0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/StringListProperty.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/StringListProperty.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.bean; @@ -22,6 +9,10 @@ public class StringListProperty { + // CHECKSTYLE:OFF + public List publicProp; + // CHECKSTYLE:ON + private List prop; public List getProp() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/StringProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/StringProperty.java index 81bf6e921b..218ce61553 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/StringProperty.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/StringProperty.java @@ -1,27 +1,17 @@ -/** - * Copyright 2012-2015 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.builtin.bean; public class StringProperty { - private String prop; + // CHECKSTYLE:OFF + public String publicProp; + // CHECKSTYLE:ON + private String prop; public String getProp() { return prop; @@ -30,5 +20,4 @@ public String getProp() { public void setProp( String prop ) { this.prop = prop; } - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/XmlGregorianCalendarFactorizedProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/XmlGregorianCalendarFactorizedProperty.java new file mode 100644 index 0000000000..afbea20a79 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/XmlGregorianCalendarFactorizedProperty.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.ap.test.builtin.bean; + +import javax.xml.datatype.XMLGregorianCalendar; + +public class XmlGregorianCalendarFactorizedProperty { + + private XMLGregorianCalendar prop; + private String control; + + XmlGregorianCalendarFactorizedProperty( String in ) { + this.control = in; + } + + public XMLGregorianCalendar getProp() { + return prop; + } + + public void setProp( XMLGregorianCalendar prop ) { + this.prop = prop; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/XmlGregorianCalendarProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/XmlGregorianCalendarProperty.java index 2b8157a1af..c15a62eb9c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/XmlGregorianCalendarProperty.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/bean/XmlGregorianCalendarProperty.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.bean; @@ -22,6 +9,10 @@ public class XmlGregorianCalendarProperty { + // CHECKSTYLE:OFF + public XMLGregorianCalendar publicProp; + // CHECKSTYLE:ON + private XMLGregorianCalendar prop; public XMLGregorianCalendar getProp() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/bean/ZonedDateTimeProperty.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/bean/ZonedDateTimeProperty.java new file mode 100644 index 0000000000..467f553077 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/bean/ZonedDateTimeProperty.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.ap.test.builtin.java8time.bean; + +import java.time.ZonedDateTime; + +/** + * @author Andreas Gudian + */ +public class ZonedDateTimeProperty { + + // CHECKSTYLE:OFF + public ZonedDateTime publicProp; + // CHECKSTYLE:ON + + private ZonedDateTime prop; + + public ZonedDateTime getProp() { + return prop; + } + + public void setProp(ZonedDateTime prop) { + this.prop = prop; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/mapper/CalendarToZonedDateTimeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/mapper/CalendarToZonedDateTimeMapper.java new file mode 100644 index 0000000000..8c860ce319 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/mapper/CalendarToZonedDateTimeMapper.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.ap.test.builtin.java8time.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin.bean.CalendarProperty; +import org.mapstruct.ap.test.builtin.java8time.bean.ZonedDateTimeProperty; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface CalendarToZonedDateTimeMapper { + CalendarToZonedDateTimeMapper INSTANCE = Mappers.getMapper( CalendarToZonedDateTimeMapper.class ); + + ZonedDateTimeProperty map(CalendarProperty source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/mapper/ZonedDateTimeToCalendarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/mapper/ZonedDateTimeToCalendarMapper.java new file mode 100644 index 0000000000..5034a05db4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/java8time/mapper/ZonedDateTimeToCalendarMapper.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.ap.test.builtin.java8time.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin.bean.CalendarProperty; +import org.mapstruct.ap.test.builtin.java8time.bean.ZonedDateTimeProperty; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface ZonedDateTimeToCalendarMapper { + ZonedDateTimeToCalendarMapper INSTANCE = Mappers.getMapper( ZonedDateTimeToCalendarMapper.class ); + + CalendarProperty map(ZonedDateTimeProperty source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/JodaTimeTest.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/JodaTimeTest.java new file mode 100644 index 0000000000..cb0c3d2378 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/JodaTimeTest.java @@ -0,0 +1,531 @@ +/* + * 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.test.builtin.jodatime; + +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDate; +import org.joda.time.LocalDateTime; +import org.joda.time.LocalTime; +import org.mapstruct.ap.test.builtin.jodatime.bean.DateTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalDateBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalDateTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.ap.test.builtin.jodatime.mapper.DateTimeToXmlGregorianCalendar; +import org.mapstruct.ap.test.builtin.jodatime.mapper.LocalDateTimeToXmlGregorianCalendar; +import org.mapstruct.ap.test.builtin.jodatime.mapper.LocalDateToXmlGregorianCalendar; +import org.mapstruct.ap.test.builtin.jodatime.mapper.LocalTimeToXmlGregorianCalendar; +import org.mapstruct.ap.test.builtin.jodatime.mapper.XmlGregorianCalendarToDateTime; +import org.mapstruct.ap.test.builtin.jodatime.mapper.XmlGregorianCalendarToLocalDate; +import org.mapstruct.ap.test.builtin.jodatime.mapper.XmlGregorianCalendarToLocalDateTime; +import org.mapstruct.ap.test.builtin.jodatime.mapper.XmlGregorianCalendarToLocalTime; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; +import org.mapstruct.ap.testutil.WithJoda; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Sjaak Derksen + */ +@WithClasses({ + DateTimeBean.class, + LocalTimeBean.class, + LocalDateBean.class, + LocalDateTimeBean.class, + XmlGregorianCalendarBean.class +}) +@IssueKey( "689" ) +@WithJoda +@WithJavaxJaxb +public class JodaTimeTest { + + @ProcessorTest + @WithClasses(DateTimeToXmlGregorianCalendar.class) + public void shouldMapDateTimeToXmlGregorianCalendar() { + + DateTimeBean in = new DateTimeBean(); + DateTime dt = new DateTime(2010, 1, 15, 1, 1, 1, 100, DateTimeZone.forOffsetHours( -1 ) ); + in.setDateTime( dt ); + XmlGregorianCalendarBean res = DateTimeToXmlGregorianCalendar.INSTANCE.toXmlGregorianCalendarBean( in ); + + assertThat( res.getxMLGregorianCalendar().getYear() ).isEqualTo( 2010 ); + assertThat( res.getxMLGregorianCalendar().getMonth() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getDay() ).isEqualTo( 15 ); + assertThat( res.getxMLGregorianCalendar().getHour() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getMinute() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getSecond() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getMillisecond() ).isEqualTo( 100 ); + assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( -60 ); + } + + @ProcessorTest + @WithClasses(DateTimeToXmlGregorianCalendar.class) + public void shouldMapIncompleteDateTimeToXmlGregorianCalendar() { + + DateTimeBean in = new DateTimeBean(); + DateTime dt = new DateTime(2010, 1, 15, 1, 1 ); + in.setDateTime( dt ); + XmlGregorianCalendarBean res = DateTimeToXmlGregorianCalendar.INSTANCE.toXmlGregorianCalendarBean( in ); + + assertThat( res.getxMLGregorianCalendar().getYear() ).isEqualTo( 2010 ); + assertThat( res.getxMLGregorianCalendar().getMonth() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getDay() ).isEqualTo( 15 ); + assertThat( res.getxMLGregorianCalendar().getHour() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getMinute() ).isEqualTo( 1 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToDateTime.class) + public void shouldMapXmlGregorianCalendarToDateTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = + DatatypeFactory.newInstance().newXMLGregorianCalendar( 2010, 1, 15, 1, 1, 1, 100, 60 ); + in.setxMLGregorianCalendar( xcal ); + + DateTimeBean res = XmlGregorianCalendarToDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getDateTime().getYear() ).isEqualTo( 2010 ); + assertThat( res.getDateTime().getMonthOfYear() ).isEqualTo( 1 ); + assertThat( res.getDateTime().getDayOfMonth() ).isEqualTo( 15 ); + assertThat( res.getDateTime().getHourOfDay() ).isEqualTo( 1 ); + assertThat( res.getDateTime().getMinuteOfHour() ).isEqualTo( 1 ); + assertThat( res.getDateTime().getSecondOfMinute() ).isEqualTo( 1 ); + assertThat( res.getDateTime().getMillisOfSecond() ).isEqualTo( 100 ); + assertThat( res.getDateTime().getZone().getOffset( null ) ).isEqualTo( 3600000 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToDateTime.class) + public void shouldMapXmlGregorianCalendarWithoutTimeZoneToDateTimeWithDefaultTimeZone() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + xcal.setSecond( 45 ); + xcal.setMillisecond( 500 ); + in.setxMLGregorianCalendar( xcal ); + + DateTimeBean res = XmlGregorianCalendarToDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getDateTime().getYear() ).isEqualTo( 1999 ); + assertThat( res.getDateTime().getMonthOfYear() ).isEqualTo( 5 ); + assertThat( res.getDateTime().getDayOfMonth() ).isEqualTo( 25 ); + assertThat( res.getDateTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getDateTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getDateTime().getSecondOfMinute() ).isEqualTo( 45 ); + assertThat( res.getDateTime().getMillisOfSecond() ).isEqualTo( 500 ); + assertThat( res.getDateTime().getZone().getOffset( 0 ) ).isEqualTo( DateTimeZone.getDefault().getOffset( 0 ) ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToDateTime.class) + public void shouldMapXmlGregorianCalendarWithoutMillisToDateTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + xcal.setSecond( 45 ); + xcal.setTimezone( 60 ); + in.setxMLGregorianCalendar( xcal ); + + DateTimeBean res = XmlGregorianCalendarToDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getDateTime().getYear() ).isEqualTo( 1999 ); + assertThat( res.getDateTime().getMonthOfYear() ).isEqualTo( 5 ); + assertThat( res.getDateTime().getDayOfMonth() ).isEqualTo( 25 ); + assertThat( res.getDateTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getDateTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getDateTime().getSecondOfMinute() ).isEqualTo( 45 ); + assertThat( res.getDateTime().getMillisOfSecond() ).isEqualTo( 0 ); + assertThat( res.getDateTime().getZone().getOffset( null ) ).isEqualTo( 3600000 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToDateTime.class) + public void shouldMapXmlGregorianCalendarWithoutMillisAndTimeZoneToDateTimeWithDefaultTimeZone() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + xcal.setSecond( 45 ); + in.setxMLGregorianCalendar( xcal ); + + DateTimeBean res = XmlGregorianCalendarToDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getDateTime().getYear() ).isEqualTo( 1999 ); + assertThat( res.getDateTime().getMonthOfYear() ).isEqualTo( 5 ); + assertThat( res.getDateTime().getDayOfMonth() ).isEqualTo( 25 ); + assertThat( res.getDateTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getDateTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getDateTime().getSecondOfMinute() ).isEqualTo( 45 ); + assertThat( res.getDateTime().getMillisOfSecond() ).isEqualTo( 0 ); + assertThat( res.getDateTime().getZone().getOffset( 0 ) ).isEqualTo( DateTimeZone.getDefault().getOffset( 0 ) ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToDateTime.class) + public void shouldMapXmlGregorianCalendarWithoutSecondsToDateTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + xcal.setTimezone( 60 ); + in.setxMLGregorianCalendar( xcal ); + + DateTimeBean res = XmlGregorianCalendarToDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getDateTime().getYear() ).isEqualTo( 1999 ); + assertThat( res.getDateTime().getMonthOfYear() ).isEqualTo( 5 ); + assertThat( res.getDateTime().getDayOfMonth() ).isEqualTo( 25 ); + assertThat( res.getDateTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getDateTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getDateTime().getSecondOfMinute() ).isEqualTo( 0 ); + assertThat( res.getDateTime().getMillisOfSecond() ).isEqualTo( 0 ); + assertThat( res.getDateTime().getZone().getOffset( null ) ).isEqualTo( 3600000 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToDateTime.class) + public void shouldMapXmlGregorianCalendarWithoutSecondsAndTimeZoneToDateTimeWithDefaultTimeZone() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + in.setxMLGregorianCalendar( xcal ); + + DateTimeBean res = XmlGregorianCalendarToDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getDateTime().getYear() ).isEqualTo( 1999 ); + assertThat( res.getDateTime().getMonthOfYear() ).isEqualTo( 5 ); + assertThat( res.getDateTime().getDayOfMonth() ).isEqualTo( 25 ); + assertThat( res.getDateTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getDateTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getDateTime().getSecondOfMinute() ).isEqualTo( 0 ); + assertThat( res.getDateTime().getMillisOfSecond() ).isEqualTo( 0 ); + assertThat( res.getDateTime().getZone().getOffset( 0 ) ).isEqualTo( DateTimeZone.getDefault().getOffset( 0 ) ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToDateTime.class) + public void shouldNotMapXmlGregorianCalendarWithoutMinutes() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + in.setxMLGregorianCalendar( xcal ); + + DateTimeBean res = XmlGregorianCalendarToDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getDateTime() ).isNull(); + } + + @ProcessorTest + @WithClasses({DateTimeToXmlGregorianCalendar.class, XmlGregorianCalendarToDateTime.class}) + public void shouldMapRoundTrip() { + + DateTimeBean dtb1 = new DateTimeBean(); + DateTime dt = new DateTime(2010, 1, 15, 1, 1, 1, 100, DateTimeZone.forOffsetHours( -1 ) ); + dtb1.setDateTime( dt ); + XmlGregorianCalendarBean xcb1 = DateTimeToXmlGregorianCalendar.INSTANCE.toXmlGregorianCalendarBean( dtb1 ); + DateTimeBean dtb2 = XmlGregorianCalendarToDateTime.INSTANCE.toDateTimeBean( xcb1 ); + XmlGregorianCalendarBean xcb2 = DateTimeToXmlGregorianCalendar.INSTANCE.toXmlGregorianCalendarBean( dtb2 ); + + assertThat( dtb1.getDateTime() ).isEqualTo( dtb2.getDateTime() ); + assertThat( xcb1.getxMLGregorianCalendar() ).isEqualTo( xcb2.getxMLGregorianCalendar() ); + + } + + @ProcessorTest + @WithClasses(LocalDateTimeToXmlGregorianCalendar.class) + public void shouldMapLocalDateTimeToXmlGregorianCalendar() { + + LocalDateTimeBean in = new LocalDateTimeBean(); + LocalDateTime dt = new LocalDateTime(2010, 1, 15, 1, 1, 1, 100 ); + in.setLocalDateTime( dt ); + XmlGregorianCalendarBean res = LocalDateTimeToXmlGregorianCalendar.INSTANCE.toXmlGregorianCalendarBean( in ); + + assertThat( res.getxMLGregorianCalendar().getYear() ).isEqualTo( 2010 ); + assertThat( res.getxMLGregorianCalendar().getMonth() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getDay() ).isEqualTo( 15 ); + assertThat( res.getxMLGregorianCalendar().getHour() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getMinute() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getSecond() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getMillisecond() ).isEqualTo( 100 ); + assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + } + + @ProcessorTest + @WithClasses(LocalDateTimeToXmlGregorianCalendar.class) + public void shouldMapIncompleteLocalDateTimeToXmlGregorianCalendar() { + + LocalDateTimeBean in = new LocalDateTimeBean(); + LocalDateTime dt = new LocalDateTime( 2010, 1, 15, 1, 1 ); + in.setLocalDateTime( dt ); + XmlGregorianCalendarBean res = LocalDateTimeToXmlGregorianCalendar.INSTANCE.toXmlGregorianCalendarBean( in ); + + assertThat( res.getxMLGregorianCalendar().getYear() ).isEqualTo( 2010 ); + assertThat( res.getxMLGregorianCalendar().getMonth() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getDay() ).isEqualTo( 15 ); + assertThat( res.getxMLGregorianCalendar().getHour() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getMinute() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getSecond() ).isEqualTo( 0 ); + assertThat( res.getxMLGregorianCalendar().getMillisecond() ).isEqualTo( 0 ); + assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalDateTime.class) + public void shouldMapXmlGregorianCalendarToLocalDateTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = + DatatypeFactory.newInstance().newXMLGregorianCalendar( 2010, 1, 15, 1, 1, 1, 100, 60 ); + in.setxMLGregorianCalendar( xcal ); + + LocalDateTimeBean res = XmlGregorianCalendarToLocalDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getLocalDateTime().getYear() ).isEqualTo( 2010 ); + assertThat( res.getLocalDateTime().getMonthOfYear() ).isEqualTo( 1 ); + assertThat( res.getLocalDateTime().getDayOfMonth() ).isEqualTo( 15 ); + assertThat( res.getLocalDateTime().getHourOfDay() ).isEqualTo( 1 ); + assertThat( res.getLocalDateTime().getMinuteOfHour() ).isEqualTo( 1 ); + assertThat( res.getLocalDateTime().getSecondOfMinute() ).isEqualTo( 1 ); + assertThat( res.getLocalDateTime().getMillisOfSecond() ).isEqualTo( 100 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalDateTime.class) + public void shouldMapXmlGregorianCalendarWithoutMillisToLocalDateTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + xcal.setSecond( 45 ); + in.setxMLGregorianCalendar( xcal ); + + LocalDateTimeBean res = XmlGregorianCalendarToLocalDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getLocalDateTime().getYear() ).isEqualTo( 1999 ); + assertThat( res.getLocalDateTime().getMonthOfYear() ).isEqualTo( 5 ); + assertThat( res.getLocalDateTime().getDayOfMonth() ).isEqualTo( 25 ); + assertThat( res.getLocalDateTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getLocalDateTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getLocalDateTime().getSecondOfMinute() ).isEqualTo( 45 ); + assertThat( res.getLocalDateTime().getMillisOfSecond() ).isEqualTo( 0 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalDateTime.class) + public void shouldMapXmlGregorianCalendarWithoutSecondsToLocalDateTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + xcal.setTimezone( 60 ); + in.setxMLGregorianCalendar( xcal ); + + LocalDateTimeBean res = XmlGregorianCalendarToLocalDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getLocalDateTime().getYear() ).isEqualTo( 1999 ); + assertThat( res.getLocalDateTime().getMonthOfYear() ).isEqualTo( 5 ); + assertThat( res.getLocalDateTime().getDayOfMonth() ).isEqualTo( 25 ); + assertThat( res.getLocalDateTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getLocalDateTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getLocalDateTime().getSecondOfMinute() ).isEqualTo( 0 ); + assertThat( res.getLocalDateTime().getMillisOfSecond() ).isEqualTo( 0 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalDateTime.class) + public void shouldNotMapXmlGregorianCalendarWithoutMinutesToLocalDateTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + in.setxMLGregorianCalendar( xcal ); + + LocalDateTimeBean res = XmlGregorianCalendarToLocalDateTime.INSTANCE.toDateTimeBean( in ); + assertThat( res.getLocalDateTime() ).isNull(); + + } + + @ProcessorTest + @WithClasses(LocalDateToXmlGregorianCalendar.class) + public void shouldMapLocalDateToXmlGregorianCalendar() { + + LocalDateBean in = new LocalDateBean(); + LocalDate dt = new LocalDate(2010, 1, 15 ); + in.setLocalDate( dt ); + XmlGregorianCalendarBean res = LocalDateToXmlGregorianCalendar.INSTANCE.toXmlGregorianCalendarBean( in ); + + assertThat( res.getxMLGregorianCalendar().getYear() ).isEqualTo( 2010 ); + assertThat( res.getxMLGregorianCalendar().getMonth() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getDay() ).isEqualTo( 15 ); + assertThat( res.getxMLGregorianCalendar().getHour() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + assertThat( res.getxMLGregorianCalendar().getMinute() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + assertThat( res.getxMLGregorianCalendar().getSecond() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + assertThat( res.getxMLGregorianCalendar().getMillisecond() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalDate.class) + public void shouldMapXmlGregorianCalendarToLocalDate() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendarDate( + 1999, + 5, + 25, + DatatypeConstants.FIELD_UNDEFINED ); + in.setxMLGregorianCalendar( xcal ); + + LocalDateBean res = XmlGregorianCalendarToLocalDate.INSTANCE.toLocalDateBean( in ); + assertThat( res.getLocalDate().getYear() ).isEqualTo( 1999 ); + assertThat( res.getLocalDate().getMonthOfYear() ).isEqualTo( 5 ); + assertThat( res.getLocalDate().getDayOfMonth() ).isEqualTo( 25 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalDate.class) + public void shouldNotMapXmlGregorianCalendarWithoutDaysToLocalDate() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendarDate( + 1999, + 5, + DatatypeConstants.FIELD_UNDEFINED, + DatatypeConstants.FIELD_UNDEFINED ); + in.setxMLGregorianCalendar( xcal ); + + LocalDateBean res = XmlGregorianCalendarToLocalDate.INSTANCE.toLocalDateBean( in ); + assertThat( res.getLocalDate() ).isNull(); + + } + + @ProcessorTest + @WithClasses(LocalTimeToXmlGregorianCalendar.class) + public void shouldMapIncompleteLocalTimeToXmlGregorianCalendar() { + + LocalTimeBean in = new LocalTimeBean(); + LocalTime dt = new LocalTime( 1, 1, 0, 100 ); + in.setLocalTime( dt ); + XmlGregorianCalendarBean res = LocalTimeToXmlGregorianCalendar.INSTANCE.toXmlGregorianCalendarBean( in ); + + assertThat( res.getxMLGregorianCalendar().getYear() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + assertThat( res.getxMLGregorianCalendar().getMonth() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + assertThat( res.getxMLGregorianCalendar().getDay() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + assertThat( res.getxMLGregorianCalendar().getHour() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getMinute() ).isEqualTo( 1 ); + assertThat( res.getxMLGregorianCalendar().getSecond() ).isEqualTo( 0 ); + assertThat( res.getxMLGregorianCalendar().getMillisecond() ).isEqualTo( 100 ); + assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalTime.class) + public void shouldMapXmlGregorianCalendarToLocalTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = + DatatypeFactory.newInstance().newXMLGregorianCalendarTime( 1, 1, 1, 100, 60 ); + in.setxMLGregorianCalendar( xcal ); + + LocalTimeBean res = XmlGregorianCalendarToLocalTime.INSTANCE.toLocalTimeBean( in ); + assertThat( res.getLocalTime().getHourOfDay() ).isEqualTo( 1 ); + assertThat( res.getLocalTime().getMinuteOfHour() ).isEqualTo( 1 ); + assertThat( res.getLocalTime().getSecondOfMinute() ).isEqualTo( 1 ); + assertThat( res.getLocalTime().getMillisOfSecond() ).isEqualTo( 100 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalTime.class) + public void shouldMapXmlGregorianCalendarWithoutMillisToLocalTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + xcal.setSecond( 45 ); + in.setxMLGregorianCalendar( xcal ); + + LocalTimeBean res = XmlGregorianCalendarToLocalTime.INSTANCE.toLocalTimeBean( in ); + assertThat( res.getLocalTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getLocalTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getLocalTime().getSecondOfMinute() ).isEqualTo( 45 ); + assertThat( res.getLocalTime().getMillisOfSecond() ).isEqualTo( 0 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalTime.class) + public void shouldMapXmlGregorianCalendarWithoutSecondsToLocalTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setHour( 23 ); + xcal.setMinute( 34 ); + xcal.setTimezone( 60 ); + in.setxMLGregorianCalendar( xcal ); + + LocalTimeBean res = XmlGregorianCalendarToLocalTime.INSTANCE.toLocalTimeBean( in ); + assertThat( res.getLocalTime().getHourOfDay() ).isEqualTo( 23 ); + assertThat( res.getLocalTime().getMinuteOfHour() ).isEqualTo( 34 ); + assertThat( res.getLocalTime().getSecondOfMinute() ).isEqualTo( 0 ); + assertThat( res.getLocalTime().getMillisOfSecond() ).isEqualTo( 0 ); + } + + @ProcessorTest + @WithClasses(XmlGregorianCalendarToLocalTime.class) + public void shouldNotMapXmlGregorianCalendarWithoutMinutesToLocalTime() throws Exception { + + XmlGregorianCalendarBean in = new XmlGregorianCalendarBean(); + XMLGregorianCalendar xcal = DatatypeFactory.newInstance().newXMLGregorianCalendar(); + xcal.setYear( 1999 ); + xcal.setMonth( 5 ); + xcal.setDay( 25 ); + xcal.setHour( 23 ); + in.setxMLGregorianCalendar( xcal ); + + LocalTimeBean res = XmlGregorianCalendarToLocalTime.INSTANCE.toLocalTimeBean( in ); + assertThat( res.getLocalTime() ).isNull(); + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/DateTimeBean.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/DateTimeBean.java new file mode 100644 index 0000000000..e250aa12df --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/DateTimeBean.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.ap.test.builtin.jodatime.bean; + +import org.joda.time.DateTime; + +/** + * + * @author Sjaak Derksen + */ +public class DateTimeBean { + + private DateTime dateTime; + + public DateTime getDateTime() { + return dateTime; + } + + public void setDateTime(DateTime dateTime) { + this.dateTime = dateTime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalDateBean.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalDateBean.java new file mode 100644 index 0000000000..3306964076 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalDateBean.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.ap.test.builtin.jodatime.bean; + +import org.joda.time.LocalDate; + +/** + * + * @author Sjaak Derksen + */ +public class LocalDateBean { + + private LocalDate localDate; + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalDateTimeBean.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalDateTimeBean.java new file mode 100644 index 0000000000..d7e968848d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalDateTimeBean.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.ap.test.builtin.jodatime.bean; + +import org.joda.time.LocalDateTime; + +/** + * + * @author Sjaak Derksen + */ +public class LocalDateTimeBean { + + private LocalDateTime localDateTime; + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalTimeBean.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalTimeBean.java new file mode 100644 index 0000000000..fd74f507b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/LocalTimeBean.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.ap.test.builtin.jodatime.bean; + +import org.joda.time.LocalTime; + +/** + * + * @author Sjaak Derksen + */ +public class LocalTimeBean { + + private LocalTime localTime; + + public LocalTime getLocalTime() { + return localTime; + } + + public void setLocalTime(LocalTime localTime) { + this.localTime = localTime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/XmlGregorianCalendarBean.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/XmlGregorianCalendarBean.java new file mode 100644 index 0000000000..7ba0fd4aed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/bean/XmlGregorianCalendarBean.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.ap.test.builtin.jodatime.bean; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * + * @author Sjaak Derksen + */ +public class XmlGregorianCalendarBean { + + XMLGregorianCalendar xMLGregorianCalendar; + + public XMLGregorianCalendar getxMLGregorianCalendar() { + return xMLGregorianCalendar; + } + + public void setxMLGregorianCalendar(XMLGregorianCalendar xMLGregorianCalendar) { + this.xMLGregorianCalendar = xMLGregorianCalendar; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/DateTimeToXmlGregorianCalendar.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/DateTimeToXmlGregorianCalendar.java new file mode 100644 index 0000000000..d56a7b5019 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/DateTimeToXmlGregorianCalendar.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.ap.test.builtin.jodatime.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.builtin.jodatime.bean.DateTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface DateTimeToXmlGregorianCalendar { + + DateTimeToXmlGregorianCalendar INSTANCE = Mappers.getMapper( DateTimeToXmlGregorianCalendar.class ); + + @Mapping( target = "xMLGregorianCalendar", source = "dateTime") + XmlGregorianCalendarBean toXmlGregorianCalendarBean( DateTimeBean in ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalDateTimeToXmlGregorianCalendar.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalDateTimeToXmlGregorianCalendar.java new file mode 100644 index 0000000000..e36808d608 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalDateTimeToXmlGregorianCalendar.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.ap.test.builtin.jodatime.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalDateTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface LocalDateTimeToXmlGregorianCalendar { + + LocalDateTimeToXmlGregorianCalendar INSTANCE = Mappers.getMapper( LocalDateTimeToXmlGregorianCalendar.class ); + + @Mapping( target = "xMLGregorianCalendar", source = "localDateTime") + XmlGregorianCalendarBean toXmlGregorianCalendarBean( LocalDateTimeBean in ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalDateToXmlGregorianCalendar.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalDateToXmlGregorianCalendar.java new file mode 100644 index 0000000000..d9a3b2ce19 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalDateToXmlGregorianCalendar.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.ap.test.builtin.jodatime.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalDateBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface LocalDateToXmlGregorianCalendar { + + LocalDateToXmlGregorianCalendar INSTANCE = Mappers.getMapper( LocalDateToXmlGregorianCalendar.class ); + + @Mapping( target = "xMLGregorianCalendar", source = "localDate") + XmlGregorianCalendarBean toXmlGregorianCalendarBean( LocalDateBean in ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalTimeToXmlGregorianCalendar.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalTimeToXmlGregorianCalendar.java new file mode 100644 index 0000000000..74b5ffda71 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/LocalTimeToXmlGregorianCalendar.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.ap.test.builtin.jodatime.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface LocalTimeToXmlGregorianCalendar { + + LocalTimeToXmlGregorianCalendar INSTANCE = Mappers.getMapper( LocalTimeToXmlGregorianCalendar.class ); + + @Mapping( target = "xMLGregorianCalendar", source = "localTime") + XmlGregorianCalendarBean toXmlGregorianCalendarBean( LocalTimeBean in ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToDateTime.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToDateTime.java new file mode 100644 index 0000000000..a173faacf1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToDateTime.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.ap.test.builtin.jodatime.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.builtin.jodatime.bean.DateTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface XmlGregorianCalendarToDateTime { + + XmlGregorianCalendarToDateTime INSTANCE = Mappers.getMapper( XmlGregorianCalendarToDateTime.class ); + + @Mapping( target = "dateTime", source = "xMLGregorianCalendar" ) + DateTimeBean toDateTimeBean( XmlGregorianCalendarBean in ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalDate.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalDate.java new file mode 100644 index 0000000000..4fa3f31d49 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalDate.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.ap.test.builtin.jodatime.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalDateBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface XmlGregorianCalendarToLocalDate { + + XmlGregorianCalendarToLocalDate INSTANCE = Mappers.getMapper( XmlGregorianCalendarToLocalDate.class ); + + @Mapping( target = "localDate", source = "xMLGregorianCalendar" ) + LocalDateBean toLocalDateBean( XmlGregorianCalendarBean in ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalDateTime.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalDateTime.java new file mode 100644 index 0000000000..b5807dfe81 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalDateTime.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.ap.test.builtin.jodatime.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalDateTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface XmlGregorianCalendarToLocalDateTime { + + XmlGregorianCalendarToLocalDateTime INSTANCE = Mappers.getMapper( XmlGregorianCalendarToLocalDateTime.class ); + + @Mapping( target = "localDateTime", source = "xMLGregorianCalendar" ) + LocalDateTimeBean toDateTimeBean( XmlGregorianCalendarBean in ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalTime.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalTime.java new file mode 100644 index 0000000000..28d082469f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/mapper/XmlGregorianCalendarToLocalTime.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.ap.test.builtin.jodatime.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.builtin.jodatime.bean.LocalTimeBean; +import org.mapstruct.ap.test.builtin.jodatime.bean.XmlGregorianCalendarBean; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface XmlGregorianCalendarToLocalTime { + + XmlGregorianCalendarToLocalTime INSTANCE = Mappers.getMapper( XmlGregorianCalendarToLocalTime.class ); + + @Mapping( target = "localTime", source = "xMLGregorianCalendar" ) + LocalTimeBean toLocalTimeBean( XmlGregorianCalendarBean in ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToDateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToDateMapper.java index 6b121f609b..a0abde8a87 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToDateMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToDateMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToStringMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToStringMapper.java index d910b91e0e..da21e10e0e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToStringMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToStringMapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Mappings; import org.mapstruct.ap.test.builtin.bean.CalendarProperty; import org.mapstruct.ap.test.builtin.bean.StringProperty; import org.mapstruct.factory.Mappers; @@ -29,6 +17,9 @@ public interface CalendarToStringMapper { CalendarToStringMapper INSTANCE = Mappers.getMapper( CalendarToStringMapper.class ); - @Mapping( target = "prop", dateFormat = "dd.MM.yyyy" ) + @Mappings( { + @Mapping( target = "prop", dateFormat = "dd.MM.yyyy" ), + @Mapping( target = "publicProp", dateFormat = "dd.MM.yyyy" ) + } ) StringProperty map( CalendarProperty source ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToXmlGregCalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToXmlGregCalMapper.java index c195a5e7f2..445507a8bb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToXmlGregCalMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/CalendarToXmlGregCalMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/DateToCalendarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/DateToCalendarMapper.java index 25b38738bb..fcc04131f7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/DateToCalendarMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/DateToCalendarMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/DateToXmlGregCalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/DateToXmlGregCalMapper.java index 4f425cb8f2..9136959ad6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/DateToXmlGregCalMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/DateToXmlGregCalMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/IterableSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/IterableSourceTargetMapper.java index 0c697f6c6f..619a72514c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/IterableSourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/IterableSourceTargetMapper.java @@ -1,32 +1,17 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; -import org.mapstruct.ap.test.builtin._target.IterableTarget; -import org.mapstruct.ap.test.builtin.source.IterableSource; - import java.util.List; - import javax.xml.datatype.XMLGregorianCalendar; import org.mapstruct.IterableMapping; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin._target.IterableTarget; +import org.mapstruct.ap.test.builtin.source.IterableSource; import org.mapstruct.factory.Mappers; @Mapper diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbListMapper.java new file mode 100644 index 0000000000..602e2180f1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbListMapper.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.ap.test.builtin.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementListProperty; +import org.mapstruct.ap.test.builtin.bean.StringListProperty; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface JakartaJaxbListMapper { + + JakartaJaxbListMapper INSTANCE = Mappers.getMapper( JakartaJaxbListMapper.class ); + + StringListProperty map(JakartaJaxbElementListProperty source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbMapper.java new file mode 100644 index 0000000000..9334792572 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JakartaJaxbMapper.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.ap.test.builtin.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin.bean.BigDecimalProperty; +import org.mapstruct.ap.test.builtin.bean.JakartaJaxbElementProperty; +import org.mapstruct.ap.test.builtin.bean.SomeType; +import org.mapstruct.ap.test.builtin.bean.SomeTypeProperty; +import org.mapstruct.ap.test.builtin.bean.StringProperty; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface JakartaJaxbMapper { + + JakartaJaxbMapper INSTANCE = Mappers.getMapper( JakartaJaxbMapper.class ); + + StringProperty map(JakartaJaxbElementProperty source); + + BigDecimalProperty mapBD(JakartaJaxbElementProperty source); + + SomeTypeProperty mapSomeType(JakartaJaxbElementProperty source); + + @SuppressWarnings( "unused" ) + default SomeType map( String in ) { + return new SomeType(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JaxbListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JaxbListMapper.java index 0ce97cfb7f..c34d956a2d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JaxbListMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JaxbListMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JaxbMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JaxbMapper.java index 0db9e4876d..ec2ca65b29 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JaxbMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/JaxbMapper.java @@ -1,25 +1,15 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin.bean.BigDecimalProperty; import org.mapstruct.ap.test.builtin.bean.JaxbElementProperty; +import org.mapstruct.ap.test.builtin.bean.SomeType; +import org.mapstruct.ap.test.builtin.bean.SomeTypeProperty; import org.mapstruct.ap.test.builtin.bean.StringProperty; import org.mapstruct.factory.Mappers; @@ -29,4 +19,12 @@ public interface JaxbMapper { JaxbMapper INSTANCE = Mappers.getMapper( JaxbMapper.class ); StringProperty map(JaxbElementProperty source); + + BigDecimalProperty mapBD(JaxbElementProperty source); + + SomeTypeProperty mapSomeType(JaxbElementProperty source); + + default SomeType map( String in ) { + return new SomeType(); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/MapSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/MapSourceTargetMapper.java index a1a46567f1..04a8f6bf0d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/MapSourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/MapSourceTargetMapper.java @@ -1,32 +1,17 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; -import org.mapstruct.ap.test.builtin._target.MapTarget; -import org.mapstruct.ap.test.builtin.source.MapSource; - import java.util.Map; - import javax.xml.bind.JAXBElement; import javax.xml.datatype.XMLGregorianCalendar; import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin._target.MapTarget; +import org.mapstruct.ap.test.builtin.source.MapSource; import org.mapstruct.factory.Mappers; @Mapper @@ -35,6 +20,6 @@ public interface MapSourceTargetMapper { MapSourceTargetMapper INSTANCE = Mappers.getMapper( MapSourceTargetMapper.class ); MapTarget sourceToTarget(MapSource source); - Map longDateMapToStringStringMap(Map, XMLGregorianCalendar> source); + Map longDateMapToStringStringMap(Map, XMLGregorianCalendar> source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/SourceTargetWithDateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/SourceTargetWithDateMapper.java deleted file mode 100644 index d4e339cf5d..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/SourceTargetWithDateMapper.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.builtin.mapper; - -import org.mapstruct.ap.test.builtin._target.TargetWithDate; -import org.mapstruct.ap.test.builtin.source.SourceWithDate; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -/** - * @author Andreas Gudian - * - */ -@Mapper -public interface SourceTargetWithDateMapper { - - SourceTargetWithDateMapper INSTANCE = Mappers.getMapper( SourceTargetWithDateMapper.class ); - - TargetWithDate toTargetWithDate(SourceWithDate source); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/SourceTargetWithSqlDateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/SourceTargetWithSqlDateMapper.java deleted file mode 100644 index d9aed50e11..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/SourceTargetWithSqlDateMapper.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.builtin.mapper; - -import org.mapstruct.Mapper; -import org.mapstruct.ap.test.builtin._target.TargetWithSqlDate; -import org.mapstruct.ap.test.builtin.source.SourceWithDate; -import org.mapstruct.factory.Mappers; - -/** - * @author Andreas Gudian - * - */ -@Mapper -public interface SourceTargetWithSqlDateMapper { - - SourceTargetWithSqlDateMapper INSTANCE = Mappers.getMapper( SourceTargetWithSqlDateMapper.class ); - - TargetWithSqlDate toTargetWithSqlDate(SourceWithDate source); - -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/StringToCalendarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/StringToCalendarMapper.java index c53107032c..31e782976e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/StringToCalendarMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/StringToCalendarMapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Mappings; import org.mapstruct.ap.test.builtin.bean.CalendarProperty; import org.mapstruct.ap.test.builtin.bean.StringProperty; import org.mapstruct.factory.Mappers; @@ -29,6 +17,9 @@ public interface StringToCalendarMapper { StringToCalendarMapper INSTANCE = Mappers.getMapper( StringToCalendarMapper.class ); - @Mapping( target = "prop", dateFormat = "dd.MM.yyyy" ) + @Mappings( { + @Mapping( target = "prop", dateFormat = "dd.MM.yyyy" ), + @Mapping( target = "publicProp", dateFormat = "dd.MM.yyyy" ) + } ) CalendarProperty map( StringProperty source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/StringToXmlGregCalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/StringToXmlGregCalMapper.java index bb1e5bb4a9..a216b0b262 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/StringToXmlGregCalMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/StringToXmlGregCalMapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Mappings; import org.mapstruct.ap.test.builtin.bean.StringProperty; import org.mapstruct.ap.test.builtin.bean.XmlGregorianCalendarProperty; import org.mapstruct.factory.Mappers; @@ -31,7 +19,10 @@ public interface StringToXmlGregCalMapper { XmlGregorianCalendarProperty map(StringProperty source); - @Mapping(target = "prop", dateFormat = "dd.MM.yyyy") + @Mappings( { + @Mapping(target = "prop", dateFormat = "dd.MM.yyyy"), + @Mapping(target = "publicProp", dateFormat = "dd.MM.yyyy") + } ) XmlGregorianCalendarProperty mapAndFormat(StringProperty source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/ToXmlGregCalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/ToXmlGregCalMapper.java new file mode 100644 index 0000000000..9ab5dc366a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/ToXmlGregCalMapper.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.ap.test.builtin.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.builtin.bean.CalendarProperty; +import org.mapstruct.ap.test.builtin.bean.DatatypeFactory; +import org.mapstruct.ap.test.builtin.bean.DateProperty; +import org.mapstruct.ap.test.builtin.bean.XmlGregorianCalendarFactorizedProperty; +import org.mapstruct.factory.Mappers; + +@Mapper( uses = DatatypeFactory.class ) +public interface ToXmlGregCalMapper { + + ToXmlGregCalMapper INSTANCE = Mappers.getMapper( ToXmlGregCalMapper.class ); + + XmlGregorianCalendarFactorizedProperty map(CalendarProperty source); + + XmlGregorianCalendarFactorizedProperty map(DateProperty source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToCalendarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToCalendarMapper.java index 562b6e87b9..29835c39b8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToCalendarMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToCalendarMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToDateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToDateMapper.java index c44636e0f2..8b6eca9512 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToDateMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToDateMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToStringMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToStringMapper.java index f531c5880c..69d1ddc961 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToStringMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/mapper/XmlGregCalToStringMapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.builtin.mapper; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Mappings; import org.mapstruct.ap.test.builtin.bean.StringProperty; import org.mapstruct.ap.test.builtin.bean.XmlGregorianCalendarProperty; import org.mapstruct.factory.Mappers; @@ -31,7 +19,10 @@ public interface XmlGregCalToStringMapper { StringProperty map(XmlGregorianCalendarProperty source); - @Mapping(target = "prop", dateFormat = "dd.MM.yyyy") + @Mappings( { + @Mapping(target = "prop", dateFormat = "dd.MM.yyyy"), + @Mapping(target = "publicProp", dateFormat = "dd.MM.yyyy") + } ) StringProperty mapAndFormat(XmlGregorianCalendarProperty source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/source/IterableSource.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/source/IterableSource.java index 240fa7187d..69a3bab2bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/source/IterableSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/source/IterableSource.java @@ -1,25 +1,11 @@ -/** - * Copyright 2012-2015 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.builtin.source; import java.util.List; - import javax.xml.datatype.XMLGregorianCalendar; /** @@ -28,6 +14,10 @@ */ public class IterableSource { + // CHECKSTYLE:OFF + public List publicDates; + // CHECKSTYLE:ON + private List dates; public List getDates() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/source/MapSource.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/source/MapSource.java index bb05164929..6b8e8f3b94 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/source/MapSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/source/MapSource.java @@ -1,25 +1,11 @@ -/** - * Copyright 2012-2015 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.builtin.source; import java.util.Map; - import javax.xml.bind.JAXBElement; import javax.xml.datatype.XMLGregorianCalendar; @@ -29,6 +15,10 @@ */ public class MapSource { + // CHECKSTYLE:OFF + public Map, XMLGregorianCalendar> publicExample; + // CHECKSTYLE:ON + private Map, XMLGregorianCalendar> example; public Map, XMLGregorianCalendar> getExample() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/builtin/source/SourceWithDate.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/source/SourceWithDate.java deleted file mode 100644 index aac8e87252..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/source/SourceWithDate.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.builtin.source; - -import java.util.Date; - -/** - * @author Andreas Gudian - * - */ -public class SourceWithDate { - private Date date; - - public Date getDate() { - return date; - } - - public void setDate(Date date) { - this.date = date; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java index 5e750eabb8..4e02045ed5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; @@ -34,7 +21,10 @@ public abstract class BaseMapper { public abstract Target sourceToTarget(Source source); - private static final List INVOCATIONS = new ArrayList(); + @Qualified + public abstract Target sourceToTargetQualified(Source source); + + private static final List INVOCATIONS = new ArrayList<>(); @BeforeMapping public void noArgsBeforeMapping() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java index cd65ddcc39..825b359152 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; @@ -25,36 +12,33 @@ import java.util.List; import java.util.Map; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.BeforeEach; import org.mapstruct.AfterMapping; import org.mapstruct.BeforeMapping; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Test for callback methods that are defined using {@link BeforeMapping} / {@link AfterMapping} * * @author Andreas Gudian */ -@RunWith( AnnotationProcessorTestRunner.class ) @WithClasses( { ClassContainingCallbacks.class, Invocation.class, Source.class, Target.class, SourceTargetMapper.class, SourceTargetCollectionMapper.class, BaseMapper.class, Qualified.class, SourceEnum.class, TargetEnum.class }) @IssueKey("14") public class CallbackMethodTest { - @Before + @BeforeEach public void reset() { ClassContainingCallbacks.reset(); BaseMapper.reset(); } - @Test + @ProcessorTest public void callbackMethodsForBeanMappingCalled() { SourceTargetMapper.INSTANCE.sourceToTarget( createSource() ); @@ -62,7 +46,7 @@ public void callbackMethodsForBeanMappingCalled() { assertBeanMappingInvocations( BaseMapper.getInvocations() ); } - @Test + @ProcessorTest public void callbackMethodsForBeanMappingWithResultParamCalled() { SourceTargetMapper.INSTANCE.sourceToTarget( createSource(), createEmptyTarget() ); @@ -70,7 +54,7 @@ public void callbackMethodsForBeanMappingWithResultParamCalled() { assertBeanMappingInvocations( BaseMapper.getInvocations() ); } - @Test + @ProcessorTest public void callbackMethodsForIterableMappingCalled() { SourceTargetCollectionMapper.INSTANCE.sourceToTarget( Arrays.asList( createSource() ) ); @@ -78,16 +62,16 @@ public void callbackMethodsForIterableMappingCalled() { assertIterableMappingInvocations( BaseMapper.getInvocations() ); } - @Test + @ProcessorTest public void callbackMethodsForIterableMappingWithResultParamCalled() { SourceTargetCollectionMapper.INSTANCE.sourceToTarget( - Arrays.asList( createSource() ), new ArrayList() ); + Arrays.asList( createSource() ), new ArrayList<>() ); assertIterableMappingInvocations( ClassContainingCallbacks.getInvocations() ); assertIterableMappingInvocations( BaseMapper.getInvocations() ); } - @Test + @ProcessorTest public void callbackMethodsForMapMappingCalled() { SourceTargetCollectionMapper.INSTANCE.sourceToTarget( toMap( "foo", createSource() ) ); @@ -95,17 +79,17 @@ public void callbackMethodsForMapMappingCalled() { assertMapMappingInvocations( BaseMapper.getInvocations() ); } - @Test + @ProcessorTest public void callbackMethodsForMapMappingWithResultParamCalled() { SourceTargetCollectionMapper.INSTANCE.sourceToTarget( toMap( "foo", createSource() ), - new HashMap() ); + new HashMap<>() ); assertMapMappingInvocations( ClassContainingCallbacks.getInvocations() ); assertMapMappingInvocations( BaseMapper.getInvocations() ); } - @Test + @ProcessorTest public void qualifiersAreEvaluatedCorrectly() { Source source = createSource(); Target target = SourceTargetMapper.INSTANCE.qualifiedSourceToTarget( source ); @@ -122,12 +106,12 @@ public void qualifiersAreEvaluatedCorrectly() { assertQualifiedInvocations( BaseMapper.getInvocations(), sourceList, targetList ); } - @Test + @ProcessorTest public void callbackMethodsForEnumMappingCalled() { SourceEnum source = SourceEnum.B; TargetEnum target = SourceTargetMapper.INSTANCE.toTargetEnum( source ); - List invocations = new ArrayList(); + List invocations = new ArrayList<>(); invocations.addAll( allBeforeMappingMethods( source, target, TargetEnum.class ) ); invocations.addAll( allAfterMappingMethods( source, target, TargetEnum.class ) ); @@ -186,7 +170,7 @@ private void assertMapMappingInvocations(List invocations) { } private Map toMap(String string, T value) { - Map result = new HashMap(); + Map result = new HashMap<>(); result.put( string, value ); return result; } @@ -204,7 +188,7 @@ private void assertCollectionMappingInvocations(List invocations, Ob } private List beanMappingInvocationList(Object source, Object target, Object emptyTarget) { - List invocations = new ArrayList(); + List invocations = new ArrayList<>(); invocations.addAll( allBeforeMappingMethods( source, emptyTarget, Target.class ) ); invocations.addAll( allAfterMappingMethods( source, target, Target.class ) ); @@ -213,7 +197,7 @@ private List beanMappingInvocationList(Object source, Object target, } private List allAfterMappingMethods(Object source, Object target, Class targetClass) { - return new ArrayList( Arrays.asList( + return new ArrayList<>( Arrays.asList( new Invocation( "noArgsAfterMapping" ), new Invocation( "withSourceAfterMapping", source ), new Invocation( "withSourceAsObjectAfterMapping", source ), @@ -224,7 +208,7 @@ private List allAfterMappingMethods(Object source, Object target, Cl } private List allBeforeMappingMethods(Object source, Object emptyTarget, Class targetClass) { - return new ArrayList( Arrays.asList( + return new ArrayList<>( Arrays.asList( new Invocation( "noArgsBeforeMapping" ), new Invocation( "withSourceBeforeMapping", source ), new Invocation( "withSourceAsObjectBeforeMapping", source ), @@ -239,7 +223,7 @@ private void assertQualifiedInvocations(List actual, Object source, } private List allQualifiedCallbackMethods(Object source, Object target) { - List invocations = new ArrayList(); + List invocations = new ArrayList<>(); invocations.add( new Invocation( "withSourceBeforeMappingQualified", source ) ); if ( source instanceof List || source instanceof Map ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ClassContainingCallbacks.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ClassContainingCallbacks.java index 6aeda33f26..2889a4ad30 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ClassContainingCallbacks.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ClassContainingCallbacks.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; @@ -31,7 +18,7 @@ * @author Andreas Gudian */ public class ClassContainingCallbacks { - private static final List INVOCATIONS = new ArrayList(); + private static final List INVOCATIONS = new ArrayList<>(); @BeforeMapping public void noArgsBeforeMapping() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/Invocation.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Invocation.java index dea2725c15..42d321af7c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/Invocation.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Invocation.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; @@ -75,13 +62,10 @@ else if ( !arguments.equals( other.arguments ) ) { return false; } if ( methodName == null ) { - if ( other.methodName != null ) { - return false; - } + return other.methodName == null; } - else if ( !methodName.equals( other.methodName ) ) { - return false; + else { + return methodName.equals( other.methodName ); } - return true; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/Qualified.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Qualified.java index 3d6e78a976..1a0f48f4b1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/Qualified.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Qualified.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/Source.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Source.java index 4c17d41700..69d91af39d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.callbacks; @@ -53,14 +40,11 @@ public boolean equals(Object obj) { } Source other = (Source) obj; if ( foo == null ) { - if ( other.foo != null ) { - return false; - } + return other.foo == null; } - else if ( !foo.equals( other.foo ) ) { - return false; + else { + return foo.equals( other.foo ); } - return true; } @Override diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceEnum.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceEnum.java index 0c4a4f460a..93e393246a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceEnum.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceEnum.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceTargetCollectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceTargetCollectionMapper.java index f0eab4df1b..2d6996b484 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceTargetCollectionMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceTargetCollectionMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceTargetMapper.java index e632c6bb8e..725bbe5c03 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/Target.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Target.java index b7b9124c7a..d800bfbcdc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.callbacks; @@ -53,14 +40,11 @@ public boolean equals(Object obj) { } Target other = (Target) obj; if ( foo == null ) { - if ( other.foo != null ) { - return false; - } + return other.foo == null; } - else if ( !foo.equals( other.foo ) ) { - return false; + else { + return foo.equals( other.foo ); } - return true; } @Override diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/TargetEnum.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/TargetEnum.java index aec8c34d69..016be4363f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/TargetEnum.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/TargetEnum.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Address.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Address.java index 9f12b1953a..503e57ae25 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Address.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Address.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/AddressDto.java index 1e299f0219..4d6a16182c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/AddressDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/AddressDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Company.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Company.java index 587c6493dd..182568a730 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Company.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Company.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyDto.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyDto.java index f18a71e0d8..3227a04970 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapper.java index cf4827fd5b..4296f07791 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapperPostProcessing.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapperPostProcessing.java index d651b85040..bb7f08a836 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapperPostProcessing.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapperPostProcessing.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; @@ -30,9 +17,9 @@ public class CompanyMapperPostProcessing { @AfterMapping public void toAddressDto(Address address, @MappingTarget AddressDto addressDto) { String addressLine = address.getAddressLine(); - int seperatorIndex = addressLine.indexOf( ";" ); - addressDto.setStreet( addressLine.substring( 0, seperatorIndex ) ); - String houseNumber = addressLine.substring( seperatorIndex + 1, addressLine.length() ); + int separatorIndex = addressLine.indexOf( ";" ); + addressDto.setStreet( addressLine.substring( 0, separatorIndex ) ); + String houseNumber = addressLine.substring( separatorIndex + 1 ); addressDto.setHouseNumber( Integer.parseInt( houseNumber ) ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Employee.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Employee.java index d0b71c230d..95a31a47a2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Employee.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/Employee.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/EmployeeDto.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/EmployeeDto.java index 34e8deb48b..55ceeb40ce 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/EmployeeDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/EmployeeDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/MappingResultPostprocessorTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/MappingResultPostprocessorTest.java index 497ed7964b..6ec9ee129d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/MappingResultPostprocessorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/MappingResultPostprocessorTest.java @@ -1,32 +1,17 @@ -/** - * Copyright 2012-2015 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.callbacks.ongeneratedmethods; import java.util.Arrays; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * @@ -44,20 +29,19 @@ CompanyMapperPostProcessing.class }) @IssueKey("183") -@RunWith(AnnotationProcessorTestRunner.class) public class MappingResultPostprocessorTest { - @Test + @ProcessorTest public void test() { // setup Address address = new Address(); - address.setAddressLine( "RoadToNowhere;5"); + address.setAddressLine( "RoadToNowhere;5" ); address.setTown( "SmallTown" ); Employee employee = new Employee(); employee.setAddress( address ); Company company = new Company(); - company.setEmployees( Arrays.asList( new Employee[] { employee } ) ); + company.setEmployees( Arrays.asList( employee ) ); // test CompanyDto companyDto = CompanyMapper.INSTANCE.toCompanyDto( company ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Attribute.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Attribute.java new file mode 100644 index 0000000000..54d7434a3c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Attribute.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.ap.test.callbacks.returning; + +/** + * @author Pascal Grün + */ +public class Attribute { + private Node node; + + private String name; + private String value; + + public Attribute() { + // default constructor for MapStruct + } + + public Attribute(String name, String value) { + this.name = name; + this.value = value; + } + + public Node getNode() { + return node; + } + + public void setNode(Node node) { + this.node = node; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "Attribute [name=" + name + ", value=" + value + "]"; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDto.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDto.java new file mode 100644 index 0000000000..7bb178ae85 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/AttributeDto.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.ap.test.callbacks.returning; + +/** + * @author Pascal Grün + */ +public class AttributeDto { + + private NodeDto node; + private String name; + private String value; + + public NodeDto getNode() { + return node; + } + + public void setNode(NodeDto node) { + this.node = node; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "AttributeDto [name=" + name + ", value=" + value + "]"; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java new file mode 100644 index 0000000000..10d2a29cd7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java @@ -0,0 +1,125 @@ +/* + * 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.test.callbacks.returning; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.AfterEach; +import org.mapstruct.ap.test.callbacks.returning.NodeMapperContext.ContextListener; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Test case for https://github.com/mapstruct/mapstruct/issues/469 + * + * @author Pascal Grün + */ +@IssueKey( "469" ) +@WithClasses( { Attribute.class, AttributeDto.class, Node.class, NodeDto.class, NodeMapperDefault.class, + NodeMapperWithContext.class, NodeMapperContext.class, Number.class, NumberMapperDefault.class, + NumberMapperContext.class, NumberMapperWithContext.class } ) +class CallbacksWithReturnValuesTest { + @AfterEach + void cleanup() { + NumberMapperContext.clearCache(); + NumberMapperContext.clearVisited(); + } + + @ProcessorTest + void mappingWithDefaultHandlingRaisesStackOverflowError() { + Node root = buildNodes(); + assertThatThrownBy( () -> NodeMapperDefault.INSTANCE.nodeToNodeDto( root ) ) + .isInstanceOf( StackOverflowError.class ); + } + + @ProcessorTest + void updatingWithDefaultHandlingRaisesStackOverflowError() { + Node root = buildNodes(); + assertThatThrownBy( () -> NodeMapperDefault.INSTANCE.nodeToNodeDto( root, new NodeDto() ) ) + .isInstanceOf( StackOverflowError.class ); + } + + @ProcessorTest + void mappingWithContextCorrectlyResolvesCycles() { + final AtomicReference contextLevel = new AtomicReference<>( null ); + ContextListener contextListener = (level, method, source, target) -> contextLevel.set( level ); + + NodeMapperContext.addContextListener( contextListener ); + try { + Node root = buildNodes(); + NodeDto rootDto = NodeMapperWithContext.INSTANCE.nodeToNodeDto( root ); + assertThat( rootDto ).isNotNull(); + assertThat( contextLevel.get() ).isEqualTo( Integer.valueOf( 1 ) ); + } + finally { + NodeMapperContext.removeContextListener( contextListener ); + } + } + + private static Node buildNodes() { + Node root = new Node( "root" ); + root.addAttribute( new Attribute( "name", "root" ) ); + + Node node1 = new Node( "node1" ); + node1.addAttribute( new Attribute( "name", "node1" ) ); + + root.addChild( node1 ); + + return root; + } + + @ProcessorTest + void numberMappingWithoutContextDoesNotUseCache() { + Number n1 = NumberMapperDefault.INSTANCE.integerToNumber( 2342 ); + Number n2 = NumberMapperDefault.INSTANCE.integerToNumber( 2342 ); + + assertThat( n1 ).isEqualTo( n2 ); + assertThat( n1 ).isNotSameAs( n2 ); + } + + @ProcessorTest + void numberMappingWithContextUsesCache() { + NumberMapperContext.putCache( new Number( 2342 ) ); + Number n1 = NumberMapperWithContext.INSTANCE.integerToNumber( 2342 ); + Number n2 = NumberMapperWithContext.INSTANCE.integerToNumber( 2342 ); + + assertThat( n1 ).isEqualTo( n2 ); + assertThat( n1 ).isSameAs( n2 ); + } + + @ProcessorTest + void numberMappingWithContextCallsVisitNumber() { + Number n1 = NumberMapperWithContext.INSTANCE.integerToNumber( 1234 ); + Number n2 = NumberMapperWithContext.INSTANCE.integerToNumber( 5678 ); + + assertThat( NumberMapperContext.getVisited() ).isEqualTo( Arrays.asList( n1, n2 ) ); + } + + @ProcessorTest + @IssueKey( "2955" ) + void numberUpdateMappingWithContextUsesCacheAndThereforeDoesNotVisitNumber() { + Number target = new Number(); + Number expectedReturn = new Number( 2342 ); + NumberMapperContext.putCache( expectedReturn ); + NumberMapperWithContext.INSTANCE.integerToNumber( 2342, target ); + + assertThat( NumberMapperContext.getVisited() ).isEmpty(); + } + + @ProcessorTest + @IssueKey( "2955" ) + void numberUpdateMappingWithContextCallsVisitNumber() { + Number target = new Number(); + NumberMapperWithContext.INSTANCE.integerToNumber( 2342, target ); + + assertThat( NumberMapperContext.getVisited() ).contains( target ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Node.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Node.java new file mode 100644 index 0000000000..2ee619c61d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Node.java @@ -0,0 +1,78 @@ +/* + * 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.test.callbacks.returning; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Pascal Grün + */ +public class Node { + private Node parent; + + private String name; + + private List children; + private List attributes; + + public Node() { + // default constructor for MapStruct + } + + public Node(String name) { + this.name = name; + this.children = new ArrayList<>(); + this.attributes = new ArrayList<>(); + } + + public Node getParent() { + return parent; + } + + public void setParent(Node parent) { + this.parent = parent; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public void addChild(Node node) { + children.add( node ); + node.setParent( this ); + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + public void addAttribute(Attribute attribute) { + attributes.add( attribute ); + attribute.setNode( this ); + } + + @Override + public String toString() { + return "Node [name=" + name + "]"; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDto.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDto.java new file mode 100644 index 0000000000..36f7a2e3b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeDto.java @@ -0,0 +1,56 @@ +/* + * 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.test.callbacks.returning; + +import java.util.List; + +/** + * @author Pascal Grün + */ +public class NodeDto { + + private NodeDto parent; + private String name; + private List children; + private List attributes; + + public NodeDto getParent() { + return parent; + } + + public void setParent(NodeDto parent) { + this.parent = parent; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + @Override + public String toString() { + return "NodeDto [name=" + name + "]"; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperContext.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperContext.java new file mode 100644 index 0000000000..325778b85e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperContext.java @@ -0,0 +1,101 @@ +/* + * 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.test.callbacks.returning; + +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +/** + * @author Pascal Grün + */ +public class NodeMapperContext { + private static final ThreadLocal LEVEL = new ThreadLocal<>(); + private static final ThreadLocal> MAPPING = new ThreadLocal<>(); + + /** Only for test-inspection */ + private static final List LISTENERS = new CopyOnWriteArrayList<>(); + + private NodeMapperContext() { + // Only allow static access + } + + @BeforeMapping + @SuppressWarnings( "unchecked" ) + public static T getInstance(Object source, @TargetType Class type) { + fireMethodCalled( LEVEL.get(), "getInstance", source, null ); + Map mapping = MAPPING.get(); + if ( mapping == null ) { + return null; + } + else { + return (T) mapping.get( source ); + } + } + + @BeforeMapping + public static void setInstance(Object source, @MappingTarget Object target) { + Integer level = LEVEL.get(); + fireMethodCalled( level, "setInstance", source, target ); + if ( level == null ) { + LEVEL.set( 1 ); + MAPPING.set( new IdentityHashMap<>() ); + } + else { + LEVEL.set( level + 1 ); + } + MAPPING.get().put( source, target ); + } + + @AfterMapping + public static void cleanup() { + Integer level = LEVEL.get(); + fireMethodCalled( level, "cleanup", null, null ); + if ( level == 1 ) { + MAPPING.set( null ); + LEVEL.set( null ); + } + else { + LEVEL.set( level - 1 ); + } + } + + /** + * Only for test-inspection + */ + static void addContextListener(ContextListener contextListener) { + LISTENERS.add( contextListener ); + } + + /** + * Only for test-inspection + */ + static void removeContextListener(ContextListener contextListener) { + LISTENERS.remove( contextListener ); + } + + /** + * Only for test-inspection + */ + private static void fireMethodCalled(Integer level, String method, Object source, Object target) { + for ( ContextListener contextListener : LISTENERS ) { + contextListener.methodCalled( level, method, source, target ); + } + } + + /** + * Only for test-inspection + */ + interface ContextListener { + void methodCalled(Integer level, String method, Object source, Object target); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperDefault.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperDefault.java new file mode 100644 index 0000000000..f633a63d11 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperDefault.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.ap.test.callbacks.returning; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Pascal Grün + */ +@Mapper +public abstract class NodeMapperDefault { + public static final NodeMapperDefault INSTANCE = Mappers.getMapper( NodeMapperDefault.class ); + + public abstract NodeDto nodeToNodeDto(Node node); + + public abstract void nodeToNodeDto(Node node, @MappingTarget NodeDto nodeDto); + + protected abstract AttributeDto attributeToAttributeDto(Attribute attribute); + + protected abstract void attributeToAttributeDto(Attribute attribute, @MappingTarget AttributeDto nodeDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperWithContext.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperWithContext.java new file mode 100644 index 0000000000..456eb1134b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NodeMapperWithContext.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.ap.test.callbacks.returning; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Pascal Grün + */ +@Mapper(uses = NodeMapperContext.class ) +public abstract class NodeMapperWithContext { + public static final NodeMapperWithContext INSTANCE = Mappers.getMapper( NodeMapperWithContext.class ); + + public abstract NodeDto nodeToNodeDto(Node node); + + public abstract void nodeToNodeDto(Node node, @MappingTarget NodeDto nodeDto); + + protected abstract AttributeDto attributeToAttributeDto(Attribute attribute); + + protected abstract void attributeToAttributeDto(Attribute attribute, @MappingTarget AttributeDto nodeDto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Number.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Number.java new file mode 100644 index 0000000000..9edd87025b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/Number.java @@ -0,0 +1,51 @@ +/* + * 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.test.callbacks.returning; + +/** + * @author Pascal Grün + */ +public class Number { + private int number; + + public Number() { + this( 0 ); + } + + public Number(int number) { + this.number = number; + } + + public void setNumber(int number) { + this.number = number; + } + + public int getNumber() { + return number; + } + + @Override + public int hashCode() { + return 31 + number; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null || getClass() != obj.getClass() ) { + return false; + } + Number other = (Number) obj; + return number == other.number; + } + + @Override + public String toString() { + return "Number[number=" + number + "]"; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperContext.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperContext.java new file mode 100644 index 0000000000..bbbe9c6bec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperContext.java @@ -0,0 +1,76 @@ +/* + * 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.test.callbacks.returning; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; + +/** + * @author Pascal Grün + */ +public class NumberMapperContext { + private static final Map CACHE = new HashMap<>(); + + private static final List VISITED = new ArrayList<>(); + + private NumberMapperContext() { + // Only allow static access + } + + public static void putCache(Number number) { + CACHE.put( number, number ); + } + + public static void clearCache() { + CACHE.clear(); + } + + public static List getVisited() { + return VISITED; + } + + public static void clearVisited() { + VISITED.clear(); + } + + @AfterMapping + public static Number getInstance(Integer source, @MappingTarget Number target) { + return CACHE.get( target ); + } + + @AfterMapping + public static T visitNumber(@MappingTarget T number) { + VISITED.add( number ); + return number; + } + + @AfterMapping + public static Map withMap(Map source, @MappingTarget Map target) { + return target; + } + + @AfterMapping + public static List withList(Set source, @MappingTarget List target) { + return target; + } + + @BeforeMapping + public static String neverCalled1(Integer integer) { + throw new IllegalStateException( "This method must never be called, because the return type does not match!" ); + } + + @AfterMapping + public static String neverCalled2(Integer integer) { + throw new IllegalStateException( "This method must never be called, because the return type does not match!" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperDefault.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperDefault.java new file mode 100644 index 0000000000..d21e67fdb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperDefault.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.ap.test.callbacks.returning; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Pascal Grün + */ +@Mapper +public abstract class NumberMapperDefault { + public static final NumberMapperDefault INSTANCE = Mappers.getMapper( NumberMapperDefault.class ); + + public abstract Number integerToNumber(Integer number); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperWithContext.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperWithContext.java new file mode 100644 index 0000000000..2a1f48e726 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/NumberMapperWithContext.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.test.callbacks.returning; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Pascal Grün + */ +@Mapper( uses = NumberMapperContext.class ) +public abstract class NumberMapperWithContext { + public static final NumberMapperWithContext INSTANCE = Mappers.getMapper( NumberMapperWithContext.class ); + + public abstract Number integerToNumber(Integer number); + + public abstract void integerToNumber(Integer number, @MappingTarget Number target); + + public abstract Map longMapToIntegerMap(Map target); + + public abstract List setToList(Set target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CallbackMethodTypeMatchingTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CallbackMethodTypeMatchingTest.java index f05fb81381..ebdca6c04c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CallbackMethodTypeMatchingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CallbackMethodTypeMatchingTest.java @@ -1,42 +1,26 @@ -/** - * Copyright 2012-2015 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.callbacks.typematching; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.callbacks.typematching.CarMapper.CarDto; import org.mapstruct.ap.test.callbacks.typematching.CarMapper.CarEntity; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * @author Andreas Gudian * */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ CarMapper.class }) public class CallbackMethodTypeMatchingTest { - @Test + @ProcessorTest public void callbackMethodAreCalled() { CarEntity carEntity = CarMapper.INSTANCE.toCarEntity( new CarDto() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CarMapper.java index 7c61b428e1..2d3105531c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CarMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/callbacks/typematching/CarMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.callbacks.typematching; diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java new file mode 100644 index 0000000000..8bac5841f1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.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.ap.test.classaccessibility; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static java.lang.reflect.Modifier.isPrivate; +import static java.lang.reflect.Modifier.isProtected; +import static java.lang.reflect.Modifier.isPublic; +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Source.class, + Target.class, + PublicAbstractionMapper.class, + PackageAbstractionMapper.class, + ForcedPublicMapper.class, + ForcedPackagePrivateMapper.class +}) +public class ClassAccessibilityTest { + + @ProcessorTest + public void shouldCreateModifierAccordingToAnnotation() throws Exception { + Class publicLike = loadForMapper( PublicAbstractionMapper.class ); + assertThat( isPublic( publicLike.getModifiers() ) ).isTrue(); + + Class packageLike = loadForMapper( PackageAbstractionMapper.class ); + assertThat( isDefault( packageLike.getModifiers() ) ).isTrue(); + + Class forcedPublic = loadForMapper( ForcedPublicMapper.class ); + assertThat( isPublic( forcedPublic.getModifiers() ) ).isTrue(); + + Class forcedDefault = loadForMapper( ForcedPackagePrivateMapper.class ); + assertThat( isDefault( forcedDefault.getModifiers() ) ).isTrue(); + } + + private static Class loadForMapper(Class mapper) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass( mapper.getName() + "Impl" ); + } + + private static boolean isDefault(int modifiers) { + return !isPublic( modifiers ) && !isProtected( modifiers ) && !isPrivate( modifiers ); + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.java new file mode 100644 index 0000000000..191aadb2a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.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.ap.test.classaccessibility; + +import org.mapstruct.ClassAccessibility; +import org.mapstruct.Mapper; + +@Mapper(accessibility = ClassAccessibility.PACKAGE_PRIVATE) +public interface ForcedPackagePrivateMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java new file mode 100644 index 0000000000..fd6ced910a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.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.ap.test.classaccessibility; + +import org.mapstruct.ClassAccessibility; +import org.mapstruct.Mapper; + +@Mapper(accessibility = ClassAccessibility.PUBLIC) +interface ForcedPublicMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java new file mode 100644 index 0000000000..d9f426b382 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java @@ -0,0 +1,14 @@ +/* + * 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.test.classaccessibility; + +import org.mapstruct.Mapper; + +@Mapper +interface PackageAbstractionMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java new file mode 100644 index 0000000000..7c1b5de10f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java @@ -0,0 +1,14 @@ +/* + * 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.test.classaccessibility; + +import org.mapstruct.Mapper; + +@Mapper +public interface PublicAbstractionMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java new file mode 100644 index 0000000000..feff965719 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/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.ap.test.classaccessibility; + +/** + * @author Raimund Klein + **/ +public class Source { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java new file mode 100644 index 0000000000..e8c50a2531 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.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.ap.test.classaccessibility; + +/** + * @author Raimund Klein + **/ +public class Target { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java index 08fab50d26..42ef2a25a3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java @@ -1,25 +1,10 @@ -/** - * Copyright 2012-2015 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.collection; -import static org.fest.assertions.Assertions.assertThat; - import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; @@ -29,17 +14,20 @@ import java.util.Map; import java.util.Set; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -@WithClasses({ Source.class, Target.class, Colour.class, SourceTargetMapper.class, TestList.class, TestMap.class }) -@RunWith(AnnotationProcessorTestRunner.class) +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses({ Source.class, Target.class, Colour.class, SourceTargetMapper.class, TestList.class, TestMap.class, + StringHolderArrayList.class, + StringHolderToLongMap.class, + StringHolder.class }) public class CollectionMappingTest { - @Test + @ProcessorTest @IssueKey("6") public void shouldMapNullList() { Source source = new Source(); @@ -50,7 +38,7 @@ public void shouldMapNullList() { assertThat( target.getStringList() ).isNull(); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapNullList() { Target target = new Target(); @@ -61,7 +49,7 @@ public void shouldReverseMapNullList() { assertThat( source.getStringList() ).isNull(); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapList() { Source source = new Source(); @@ -73,7 +61,7 @@ public void shouldMapList() { assertThat( target.getStringList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("92") public void shouldMapListWithoutSetter() { Source source = new Source(); @@ -85,7 +73,7 @@ public void shouldMapListWithoutSetter() { assertThat( target.getStringListNoSetter() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapList() { Target target = new Target(); @@ -97,7 +85,7 @@ public void shouldReverseMapList() { assertThat( source.getStringList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapListAsCopy() { Source source = new Source(); @@ -110,7 +98,7 @@ public void shouldMapListAsCopy() { assertThat( source.getStringList() ).isNotEqualTo( target.getStringList() ); } - @Test + @ProcessorTest @IssueKey( "153" ) public void shouldMapListWithClearAndAddAll() { Source source = new Source(); @@ -122,7 +110,7 @@ public void shouldMapListWithClearAndAddAll() { assertThat( source.getOtherStringList() ).containsExactly( "Bob", "Alice" ); // prepare a test list to monitor add all behaviour - List testList = new TestList(); + List testList = new TestList<>(); testList.addAll( target.getOtherStringList() ); TestList.setAddAllCalled( false ); target.setOtherStringList( testList ); @@ -139,7 +127,7 @@ public void shouldMapListWithClearAndAddAll() { TestList.setAddAllCalled( false ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapListAsCopy() { Target target = new Target(); @@ -151,11 +139,11 @@ public void shouldReverseMapListAsCopy() { assertThat( target.getStringList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapArrayList() { Source source = new Source(); - source.setStringArrayList( new ArrayList( Arrays.asList( "Bob", "Alice" ) ) ); + source.setStringArrayList( new ArrayList<>( Arrays.asList( "Bob", "Alice" ) ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -163,11 +151,11 @@ public void shouldMapArrayList() { assertThat( target.getStringArrayList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapArrayList() { Target target = new Target(); - target.setStringArrayList( new ArrayList( Arrays.asList( "Bob", "Alice" ) ) ); + target.setStringArrayList( new ArrayList<>( Arrays.asList( "Bob", "Alice" ) ) ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -175,11 +163,11 @@ public void shouldReverseMapArrayList() { assertThat( source.getStringArrayList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapSet() { Source source = new Source(); - source.setStringSet( new HashSet( Arrays.asList( "Bob", "Alice" ) ) ); + source.setStringSet( new HashSet<>( Arrays.asList( "Bob", "Alice" ) ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -187,11 +175,11 @@ public void shouldMapSet() { assertThat( target.getStringSet() ).contains( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapSet() { Target target = new Target(); - target.setStringSet( new HashSet( Arrays.asList( "Bob", "Alice" ) ) ); + target.setStringSet( new HashSet<>( Arrays.asList( "Bob", "Alice" ) ) ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -199,11 +187,11 @@ public void shouldReverseMapSet() { assertThat( source.getStringSet() ).contains( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapSetAsCopy() { Source source = new Source(); - source.setStringSet( new HashSet( Arrays.asList( "Bob", "Alice" ) ) ); + source.setStringSet( new HashSet<>( Arrays.asList( "Bob", "Alice" ) ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); target.getStringSet().add( "Bill" ); @@ -211,11 +199,11 @@ public void shouldMapSetAsCopy() { assertThat( source.getStringSet() ).containsOnly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapHashSetAsCopy() { Source source = new Source(); - source.setStringHashSet( new HashSet( Arrays.asList( "Bob", "Alice" ) ) ); + source.setStringHashSet( new HashSet<>( Arrays.asList( "Bob", "Alice" ) ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); target.getStringHashSet().add( "Bill" ); @@ -223,11 +211,11 @@ public void shouldMapHashSetAsCopy() { assertThat( source.getStringHashSet() ).containsOnly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapSetAsCopy() { Target target = new Target(); - target.setStringSet( new HashSet( Arrays.asList( "Bob", "Alice" ) ) ); + target.setStringSet( new HashSet<>( Arrays.asList( "Bob", "Alice" ) ) ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); source.getStringSet().add( "Bill" ); @@ -235,7 +223,7 @@ public void shouldReverseMapSetAsCopy() { assertThat( target.getStringSet() ).containsOnly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapListToCollection() { Source source = new Source(); @@ -247,7 +235,7 @@ public void shouldMapListToCollection() { assertThat( target.getIntegerCollection() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapListToCollection() { Target target = new Target(); @@ -259,11 +247,11 @@ public void shouldReverseMapListToCollection() { assertThat( source.getIntegerList() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapIntegerSetToRawSet() { Source source = new Source(); - source.setIntegerSet( new HashSet( Arrays.asList( 1, 2 ) ) ); + source.setIntegerSet( new HashSet<>( Arrays.asList( 1, 2 ) ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -271,11 +259,11 @@ public void shouldMapIntegerSetToRawSet() { assertThat( target.getSet() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapIntegerSetToStringSet() { Source source = new Source(); - source.setAnotherIntegerSet( new HashSet( Arrays.asList( 1, 2 ) ) ); + source.setAnotherIntegerSet( new HashSet<>( Arrays.asList( 1, 2 ) ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -283,11 +271,11 @@ public void shouldMapIntegerSetToStringSet() { assertThat( target.getAnotherStringSet() ).containsOnly( "1", "2" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapIntegerSetToStringSet() { Target target = new Target(); - target.setAnotherStringSet( new HashSet( Arrays.asList( "1", "2" ) ) ); + target.setAnotherStringSet( new HashSet<>( Arrays.asList( "1", "2" ) ) ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -295,7 +283,7 @@ public void shouldReverseMapIntegerSetToStringSet() { assertThat( source.getAnotherIntegerSet() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapSetOfEnumToStringSet() { Source source = new Source(); @@ -307,11 +295,11 @@ public void shouldMapSetOfEnumToStringSet() { assertThat( target.getColours() ).containsOnly( "BLUE", "GREEN" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapSetOfEnumToStringSet() { Target target = new Target(); - target.setColours( new HashSet( Arrays.asList( "BLUE", "GREEN" ) ) ); + target.setColours( new HashSet<>( Arrays.asList( "BLUE", "GREEN" ) ) ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -319,11 +307,11 @@ public void shouldReverseMapSetOfEnumToStringSet() { assertThat( source.getColours() ).containsOnly( Colour.GREEN, Colour.BLUE ); } - @Test + @ProcessorTest public void shouldMapMapAsCopy() { Source source = new Source(); - Map map = new HashMap(); + Map map = new HashMap<>(); map.put( "Bob", 123L ); map.put( "Alice", 456L ); source.setStringLongMap( map ); @@ -335,12 +323,12 @@ public void shouldMapMapAsCopy() { assertThat( target.getStringLongMap() ).hasSize( 3 ); } - @Test + @ProcessorTest @IssueKey( "153" ) public void shouldMapMapWithClearAndPutAll() { Source source = new Source(); - Map map = new HashMap(); + Map map = new HashMap<>(); map.put( "Bob", 123L ); map.put( "Alice", 456L ); source.setOtherStringLongMap( map ); @@ -354,7 +342,7 @@ public void shouldMapMapWithClearAndPutAll() { source.getOtherStringLongMap().remove( "Alice" ); // prepare a test list to monitor add all behaviour - Map originalInstance = new TestMap(); + Map originalInstance = new TestMap<>(); originalInstance.putAll( target.getOtherStringLongMap() ); TestMap.setPuttAllCalled( false ); target.setOtherStringLongMap( originalInstance ); @@ -367,13 +355,82 @@ public void shouldMapMapWithClearAndPutAll() { TestMap.setPuttAllCalled( false ); } - @Test + @ProcessorTest @IssueKey("87") public void shouldMapIntegerSetToNumberSet() { Set numbers = SourceTargetMapper.INSTANCE - .integerSetToNumberSet( new HashSet( Arrays.asList( 123, 456 ) ) ); + .integerSetToNumberSet( new HashSet<>( Arrays.asList( 123, 456 ) ) ); assertThat( numbers ).isNotNull(); assertThat( numbers ).containsOnly( 123, 456 ); } + + @ProcessorTest + @IssueKey("732") + public void shouldEnumSetAsCopy() { + Source source = new Source(); + source.setEnumSet( EnumSet.of( Colour.BLUE, Colour.GREEN ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + source.getEnumSet().add( Colour.RED ); + + assertThat( source.getEnumSet() ).containsOnly( Colour.BLUE, Colour.GREEN, Colour.RED ); + assertThat( target.getEnumSet() ).containsOnly( Colour.BLUE, Colour.GREEN ); + } + + @ProcessorTest + @IssueKey("853") + public void shouldMapNonGenericList() { + Source source = new Source(); + source.setStringList3( new ArrayList<>( Arrays.asList( "Bob", "Alice" ) ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNonGenericStringList() ).containsExactly( + new StringHolder( "Bob" ), + new StringHolder( "Alice" ) ); + + // Inverse direction + Target newTarget = new Target(); + StringHolderArrayList nonGenericStringList = new StringHolderArrayList(); + nonGenericStringList.addAll( Arrays.asList( new StringHolder( "Bill" ), new StringHolder( "Bob" ) ) ); + newTarget.setNonGenericStringList( nonGenericStringList ); + + Source mappedSource = SourceTargetMapper.INSTANCE.targetToSource( newTarget ); + + assertThat( mappedSource ).isNotNull(); + assertThat( mappedSource.getStringList3() ).containsExactly( "Bill", "Bob" ); + } + + @ProcessorTest + @IssueKey("853") + public void shouldMapNonGenericMap() { + Source source = new Source(); + Map map = new HashMap<>(); + map.put( "Bob", 123L ); + map.put( "Alice", 456L ); + source.setStringLongMapForNonGeneric( map ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNonGenericMapStringtoLong() ).contains( + entry( new StringHolder( "Bob" ), 123L ), + entry( new StringHolder( "Alice" ), 456L ) ); + + // Inverse direction + Target newTarget = new Target(); + StringHolderToLongMap stringToLongMap = new StringHolderToLongMap(); + stringToLongMap.put( new StringHolder( "Blue" ), 321L ); + stringToLongMap.put( new StringHolder( "Green" ), 654L ); + newTarget.setNonGenericMapStringtoLong( stringToLongMap ); + + Source mappedSource = SourceTargetMapper.INSTANCE.targetToSource( newTarget ); + + assertThat( mappedSource ).isNotNull(); + assertThat( mappedSource.getStringLongMapForNonGeneric() ).contains( + entry( "Blue", 321L ), + entry( "Green", 654L ) ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/Colour.java b/processor/src/test/java/org/mapstruct/ap/test/collection/Colour.java index db961e892c..b9cb1870f4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/Colour.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/Colour.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/Source.java index 5d7f271780..0ceb45af7d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/Source.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.collection; import java.util.ArrayList; import java.util.Collection; +import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -27,6 +15,8 @@ public class Source { + private List publicStringList; + private List stringList; private List otherStringList; private ArrayList stringArrayList; @@ -48,10 +38,24 @@ public class Source { private Map otherStringLongMap; + private Map stringLongMapForNonGeneric; + private List stringList2; private Set stringSet2; + private EnumSet enumSet; + + private List stringList3; + + public List getPublicStringList() { + return publicStringList; + } + + public void setPublicStringList(List publicStringList) { + this.publicStringList = publicStringList; + } + public List getStringList() { return stringList; } @@ -164,4 +168,27 @@ public void setStringSet2(Set stringSet2) { this.stringSet2 = stringSet2; } + public EnumSet getEnumSet() { + return enumSet; + } + + public void setEnumSet(EnumSet enumSet) { + this.enumSet = enumSet; + } + + public List getStringList3() { + return stringList3; + } + + public void setStringList3(List stringList3) { + this.stringList3 = stringList3; + } + + public Map getStringLongMapForNonGeneric() { + return stringLongMapForNonGeneric; + } + + public void setStringLongMapForNonGeneric(Map stringLongMapForNonGeneric) { + this.stringLongMapForNonGeneric = stringLongMapForNonGeneric; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java index 1249803e5b..1782ebdb57 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection; @@ -29,34 +16,42 @@ import org.mapstruct.factory.Mappers; @Mapper -public interface SourceTargetMapper { +public abstract class SourceTargetMapper { - SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + static final SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mappings({ - @Mapping(source = "integerList", target = "integerCollection"), - @Mapping(source = "integerSet", target = "set"), - @Mapping(source = "anotherIntegerSet", target = "anotherStringSet"), - @Mapping(source = "stringList2", target = "stringListNoSetter"), - @Mapping(source = "stringSet2", target = "stringListNoSetter2") + @Mapping(target = "integerCollection", source = "integerList"), + @Mapping(target = "set", source = "integerSet"), + @Mapping(target = "anotherStringSet", source = "anotherIntegerSet"), + @Mapping(target = "stringListNoSetter", source = "stringList2"), + @Mapping(target = "stringListNoSetter2", source = "stringSet2"), + @Mapping(target = "nonGenericStringList", source = "stringList3"), + @Mapping(target = "nonGenericMapStringtoLong", source = "stringLongMapForNonGeneric") }) - Target sourceToTarget(Source source); + public abstract Target sourceToTarget(Source source); @InheritInverseConfiguration( name = "sourceToTarget" ) - Source targetToSource(Target target); + public abstract Source targetToSource(Target target); @InheritConfiguration - Target sourceToTargetTwoArg(Source source, @MappingTarget Target target); - - Set integerSetToStringSet(Set integers); + public abstract Target sourceToTargetTwoArg(Source source, @MappingTarget Target target); @InheritInverseConfiguration - Set stringSetToIntegerSet(Set strings); + public abstract Set stringSetToIntegerSet(Set strings); - Set colourSetToStringSet(Set colours); + public abstract Set colourSetToStringSet(Set colours); @InheritInverseConfiguration - Set stringSetToColourSet(Set colours); + public abstract Set stringSetToColourSet(Set colours); + + public abstract Set integerSetToNumberSet(Set integers); + + protected StringHolder toStringHolder(String string) { + return new StringHolder( string ); + } - Set integerSetToNumberSet(Set integers); + protected String toString(StringHolder string) { + return string.getString(); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolder.java b/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolder.java new file mode 100644 index 0000000000..af3539ff9e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolder.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.ap.test.collection; + +/** + * @author Andreas Gudian + * + */ +public class StringHolder { + private final String string; + + public StringHolder(String string) { + this.string = string; + } + + public String getString() { + return string; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( string == null ) ? 0 : string.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + StringHolder other = (StringHolder) obj; + if ( string == null ) { + return other.string == null; + } + else { + return string.equals( other.string ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolderArrayList.java b/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolderArrayList.java new file mode 100644 index 0000000000..6b3b4fd4b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolderArrayList.java @@ -0,0 +1,16 @@ +/* + * 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.test.collection; + +import java.util.ArrayList; + +/** + * @author Stefan May + */ +public class StringHolderArrayList extends ArrayList { + + private static final long serialVersionUID = 1L; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolderToLongMap.java b/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolderToLongMap.java new file mode 100644 index 0000000000..9705f2b12f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolderToLongMap.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.ap.test.collection; + +import java.util.HashMap; + +/** + * @author Stefan May + */ +public class StringHolderToLongMap extends HashMap { + + private static final long serialVersionUID = 1L; + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/Target.java index 30c2c31cff..2ff6cbfb2c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/Target.java @@ -1,35 +1,25 @@ -/** - * Copyright 2012-2015 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.collection; import java.util.ArrayList; import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; - public class Target { + //CHECKSTYLE:OFF + public List publicStringList; + //CHECKSTYLE:On + private List stringList; private List otherStringList; private ArrayList stringArrayList; @@ -52,14 +42,21 @@ public class Target { private List stringListNoSetter2; - @SuppressWarnings("rawtypes") + @SuppressWarnings( "rawtypes" ) private Set set; + private EnumSet enumSet; + + private StringHolderArrayList nonGenericStringList; + + private StringHolderToLongMap nonGenericMapStringtoLong; + public Target() { - otherStringLongMap = Maps.newHashMap(); + otherStringLongMap = new HashMap<>(); otherStringLongMap.put( "not-present-after-mapping", 42L ); - otherStringList = Lists.newArrayList( "not-present-after-mapping" ); + otherStringList = new ArrayList<>(); + otherStringList.add( "not-present-after-mapping" ); } public List getStringList() { @@ -146,14 +143,14 @@ public void setStringLongMap(Map stringLongMap) { public List getStringListNoSetter() { if ( stringListNoSetter == null ) { - stringListNoSetter = new ArrayList(); + stringListNoSetter = new ArrayList<>(); } return stringListNoSetter; } public List getStringListNoSetter2() { if ( stringListNoSetter2 == null ) { - stringListNoSetter2 = new ArrayList(); + stringListNoSetter2 = new ArrayList<>(); } return stringListNoSetter2; } @@ -174,4 +171,27 @@ public void setOtherStringList(List otherStringList) { this.otherStringList = otherStringList; } + public EnumSet getEnumSet() { + return enumSet; + } + + public void setEnumSet(EnumSet enumSet) { + this.enumSet = enumSet; + } + + public StringHolderArrayList getNonGenericStringList() { + return nonGenericStringList; + } + + public void setNonGenericStringList(StringHolderArrayList nonGenericStringList) { + this.nonGenericStringList = nonGenericStringList; + } + + public StringHolderToLongMap getNonGenericMapStringtoLong() { + return nonGenericMapStringtoLong; + } + + public void setNonGenericMapStringtoLong(StringHolderToLongMap nonGenericMapStringtoLong) { + this.nonGenericMapStringtoLong = nonGenericMapStringtoLong; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/TestList.java b/processor/src/test/java/org/mapstruct/ap/test/collection/TestList.java index 17ce775791..68741e5b84 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/TestList.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/TestList.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/TestMap.java b/processor/src/test/java/org/mapstruct/ap/test/collection/TestMap.java index 2366153013..6ff922fc1e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/TestMap.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/TestMap.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/AdderTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/AdderTest.java index 50687e6ee4..b555970770 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/AdderTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/AdderTest.java @@ -1,30 +1,14 @@ -/** - * Copyright 2012-2015 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.test.collection.adder; import java.util.ArrayList; import java.util.Arrays; -import static org.fest.assertions.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.collection.adder._target.AdderUsageObserver; import org.mapstruct.ap.test.collection.adder._target.IndoorPet; import org.mapstruct.ap.test.collection.adder._target.OutdoorPet; @@ -35,19 +19,23 @@ import org.mapstruct.ap.test.collection.adder._target.TargetHuman; import org.mapstruct.ap.test.collection.adder._target.TargetOnlyGetter; import org.mapstruct.ap.test.collection.adder._target.TargetViaTargetType; +import org.mapstruct.ap.test.collection.adder._target.TargetWithAnimals; import org.mapstruct.ap.test.collection.adder._target.TargetWithoutSetter; +import org.mapstruct.ap.test.collection.adder.source.Foo; import org.mapstruct.ap.test.collection.adder.source.SingleElementSource; import org.mapstruct.ap.test.collection.adder.source.Source; +import org.mapstruct.ap.test.collection.adder.source.Source2; import org.mapstruct.ap.test.collection.adder.source.SourceTeeth; +import org.mapstruct.ap.test.collection.adder.source.SourceWithPets; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import org.mapstruct.ap.test.collection.adder.source.Foo; -import org.mapstruct.ap.test.collection.adder.source.Source2; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Sjaak Derksen @@ -55,15 +43,18 @@ @WithClasses({ Source.class, SourceTeeth.class, + SourceWithPets.class, Target.class, TargetDali.class, TargetHuman.class, TargetOnlyGetter.class, TargetViaTargetType.class, TargetWithoutSetter.class, + TargetWithAnimals.class, SourceTargetMapper.class, SourceTargetMapperStrategyDefault.class, SourceTargetMapperStrategySetterPreferred.class, + SourceTargetMapperWithDifferentProperties.class, SingleElementSource.class, PetMapper.class, TeethMapper.class, @@ -72,13 +63,23 @@ IndoorPet.class, OutdoorPet.class, DogException.class, - CatException.class + CatException.class, + Target2.class, + Source2.class, + Source2Target2Mapper.class, + Foo.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class AdderTest { + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + SourceTargetMapper.class, + SourceTargetMapperStrategyDefault.class, + SourceTargetMapperStrategySetterPreferred.class + ); + @IssueKey("241") - @Test + @ProcessorTest public void testAdd() throws DogException { AdderUsageObserver.setUsed( false ); @@ -92,36 +93,38 @@ public void testAdd() throws DogException { assertTrue( AdderUsageObserver.isUsed() ); } - @Test(expected = DogException.class) + @ProcessorTest public void testAddWithExceptionInThrowsClause() throws DogException { AdderUsageObserver.setUsed( false ); Source source = new Source(); source.setPets( Arrays.asList( "dog" ) ); - SourceTargetMapper.INSTANCE.toTarget( source ); + assertThatThrownBy( () -> SourceTargetMapper.INSTANCE.toTarget( source ) ) + .isInstanceOf( DogException.class ); } - @Test(expected = RuntimeException.class) + @ProcessorTest public void testAddWithExceptionNotInThrowsClause() throws DogException { AdderUsageObserver.setUsed( false ); Source source = new Source(); source.setPets( Arrays.asList( "cat" ) ); - SourceTargetMapper.INSTANCE.toTarget( source ); + assertThatThrownBy( () -> SourceTargetMapper.INSTANCE.toTarget( source ) ) + .isInstanceOf( RuntimeException.class ); } @IssueKey("241") - @Test - public void testAddwithExistingTarget() throws DogException { + @ProcessorTest + public void testAddWithExistingTarget() { AdderUsageObserver.setUsed( false ); Source source = new Source(); source.setPets( Arrays.asList( "mouse" ) ); Target target = new Target(); - target.setPets( new ArrayList( Arrays.asList( 1L ) ) ); + target.setPets( new ArrayList<>( Arrays.asList( 1L ) ) ); SourceTargetMapper.INSTANCE.toExistingTarget( source, target ); assertThat( target ).isNotNull(); @@ -131,7 +134,7 @@ public void testAddwithExistingTarget() throws DogException { assertTrue( AdderUsageObserver.isUsed() ); } - @Test + @ProcessorTest public void testShouldUseDefaultStrategy() throws DogException { AdderUsageObserver.setUsed( false ); @@ -145,7 +148,7 @@ public void testShouldUseDefaultStrategy() throws DogException { assertFalse( AdderUsageObserver.isUsed() ); } - @Test + @ProcessorTest public void testShouldPreferSetterStrategyButThereIsNone() throws DogException { AdderUsageObserver.setUsed( false ); @@ -159,7 +162,7 @@ public void testShouldPreferSetterStrategyButThereIsNone() throws DogException { assertTrue( AdderUsageObserver.isUsed() ); } - @Test + @ProcessorTest public void testShouldPreferHumanSingular() { AdderUsageObserver.setUsed( false ); @@ -174,8 +177,8 @@ public void testShouldPreferHumanSingular() { assertTrue( AdderUsageObserver.isUsed() ); } - @Test - public void testshouldFallBackToDaliSingularInAbsenseOfHumanSingular() { + @ProcessorTest + public void testShouldFallBackToDaliSingularInAbsenceOfHumanSingular() { AdderUsageObserver.setUsed( false ); SourceTeeth source = new SourceTeeth(); @@ -188,8 +191,8 @@ public void testshouldFallBackToDaliSingularInAbsenseOfHumanSingular() { assertTrue( AdderUsageObserver.isUsed() ); } - @Test - public void testAddReverse() throws DogException { + @ProcessorTest + public void testAddReverse() { AdderUsageObserver.setUsed( false ); Target source = new Target(); @@ -201,7 +204,7 @@ public void testAddReverse() throws DogException { assertThat( target.getPets().get( 0 ) ).isEqualTo( "cat" ); } - @Test + @ProcessorTest public void testAddOnlyGetter() throws DogException { AdderUsageObserver.setUsed( false ); @@ -215,8 +218,8 @@ public void testAddOnlyGetter() throws DogException { assertTrue( AdderUsageObserver.isUsed() ); } - @Test - public void testAddViaTargetType() throws DogException { + @ProcessorTest + public void testAddViaTargetType() { AdderUsageObserver.setUsed( false ); Source source = new Source(); @@ -231,8 +234,8 @@ public void testAddViaTargetType() throws DogException { } @IssueKey("242") - @Test - public void testSingleElementSource() throws DogException { + @ProcessorTest + public void testSingleElementSource() { AdderUsageObserver.setUsed( false ); SingleElementSource source = new SingleElementSource(); @@ -246,14 +249,9 @@ public void testSingleElementSource() throws DogException { } @IssueKey( "310" ) - @Test - @WithClasses( { - Target2.class, - Source2.class, - Source2Target2Mapper.class, - Foo.class - } ) - public void testMissingImport() throws DogException { + @ProcessorTest + public void testMissingImport() { + generatedSource.addComparisonToFixtureFor( Source2Target2Mapper.class ); Source2 source = new Source2(); source.setAttributes( Arrays.asList( new Foo() ) ); @@ -262,4 +260,17 @@ public void testMissingImport() throws DogException { assertThat( target ).isNotNull(); assertThat( target.getAttributes().size() ).isEqualTo( 1 ); } + + @IssueKey("1478") + @ProcessorTest + public void useIterationNameFromSource() { + generatedSource.addComparisonToFixtureFor( SourceTargetMapperWithDifferentProperties.class ); + + SourceWithPets source = new SourceWithPets(); + source.setPets( Arrays.asList( "dog", "cat" ) ); + + TargetWithAnimals target = SourceTargetMapperWithDifferentProperties.INSTANCE.map( source ); + + assertThat( target.getAnimals() ).containsExactly( "dog", "cat" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/CatException.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/CatException.java index f4f7ae5fa2..970f7d7368 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/CatException.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/CatException.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/DogException.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/DogException.java index d1880132ed..e28c2749c5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/DogException.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/DogException.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/PetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/PetMapper.java index 0f53407ec4..601b62c6b6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/PetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/PetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder; @@ -23,7 +10,6 @@ import java.util.Map; import com.google.common.collect.ImmutableMap; - import org.mapstruct.TargetType; import org.mapstruct.ap.test.collection.adder._target.IndoorPet; import org.mapstruct.ap.test.collection.adder._target.OutdoorPet; @@ -44,7 +30,6 @@ public class PetMapper { .put( 3L, "cat" ) .put( 4L, "dog" ).build(); - /** * method to be used when using an adder * @@ -76,7 +61,7 @@ else if ( "dog".equals( pet ) ) { * @throws DogException */ public List toPets(List pets) throws CatException, DogException { - List result = new ArrayList(); + List result = new ArrayList<>(); for ( String pet : pets ) { result.add( toPet( pet ) ); } @@ -95,7 +80,7 @@ public T toPet(String pet, @TargetType Class clazz) throws Ca } public List toSourcePets(List pets) throws CatException, DogException { - List result = new ArrayList(); + List result = new ArrayList<>(); for ( Long pet : pets ) { result.add( PETS_TO_SOURCE.get( pet ) ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/Source2Target2Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/Source2Target2Mapper.java index ea140b0fbb..a0be9730fe 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/Source2Target2Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/Source2Target2Mapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapper.java index e053a29afd..18b542e99d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder; @@ -55,6 +42,6 @@ public interface SourceTargetMapper { TargetViaTargetType toTargetViaTargetType(Source source); - @Mapping(source = "pet", target = "pets") + @Mapping(target = "pets", source = "pet") Target fromSingleElementSource(SingleElementSource source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperStrategyDefault.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperStrategyDefault.java index 3a6ead9e96..4a824fa768 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperStrategyDefault.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperStrategyDefault.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperStrategySetterPreferred.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperStrategySetterPreferred.java index e42086f27f..93a5a7e531 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperStrategySetterPreferred.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperStrategySetterPreferred.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperWithDifferentProperties.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperWithDifferentProperties.java new file mode 100644 index 0000000000..b0460c2650 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapperWithDifferentProperties.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.ap.test.collection.adder; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.collection.adder._target.TargetWithAnimals; +import org.mapstruct.ap.test.collection.adder.source.SourceWithPets; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED) +public interface SourceTargetMapperWithDifferentProperties { + + SourceTargetMapperWithDifferentProperties INSTANCE = + Mappers.getMapper( SourceTargetMapperWithDifferentProperties.class ); + + @Mapping(target = "animals", source = "pets") + TargetWithAnimals map(SourceWithPets source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/TeethMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/TeethMapper.java index 50308fa9b7..06e416828b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/TeethMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/TeethMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/AdderUsageObserver.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/AdderUsageObserver.java index 3b2cd80b86..c579b8e40a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/AdderUsageObserver.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/AdderUsageObserver.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/IndoorPet.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/IndoorPet.java index 0045f34966..954ced0938 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/IndoorPet.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/IndoorPet.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/OutdoorPet.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/OutdoorPet.java index 7e89bf1935..8cb2475e6e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/OutdoorPet.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/OutdoorPet.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; @@ -36,6 +23,4 @@ public Long getValue() { public void setValue(Long value) { this.value = value; } - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Pet.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Pet.java index 43bb01308a..145f2b85ad 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Pet.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Pet.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target.java index 9c6106cf42..b131bd96ea 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; @@ -37,21 +24,21 @@ public void setPets(List pets) { } public void addCat(Long cat) { - // dummy method to test selection mechanims + // dummy method to test selection mechanism } public void addDog(Long cat) { - // dummy method to test selection mechanims + // dummy method to test selection mechanism } public void addPets(Long cat) { - // dummy method to test selection mechanims + // dummy method to test selection mechanism } public Long addPet(Long pet) { AdderUsageObserver.setUsed( true ); if ( pets == null ) { - pets = new ArrayList(); + pets = new ArrayList<>(); } pets.add( pet ); return pet; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target2.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target2.java index e8c74de5c3..bd6e8f9979 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target2.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; @@ -29,7 +16,7 @@ */ public class Target2 { - private List attributes = new ArrayList(); + private List attributes = new ArrayList<>(); public Foo addAttribute( Foo foo ) { attributes.add( foo ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetDali.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetDali.java index 23e61af509..30f11f3807 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetDali.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetDali.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; @@ -39,7 +26,7 @@ public void setTeeth(List teeth) { public void addTeeth(Integer tooth) { AdderUsageObserver.setUsed( true ); if ( teeth == null ) { - teeth = new ArrayList(); + teeth = new ArrayList<>(); } teeth.add( tooth ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetHuman.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetHuman.java index f71856baa5..3a2efb9b81 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetHuman.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetHuman.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; @@ -39,14 +26,14 @@ public void setTeeth(List teeth) { public void addTooth(Integer pet) { AdderUsageObserver.setUsed( true ); if ( teeth == null ) { - teeth = new ArrayList(); + teeth = new ArrayList<>(); } teeth.add( pet ); } public void addTeeth(Integer tooth) { if ( teeth == null ) { - teeth = new ArrayList(); + teeth = new ArrayList<>(); } teeth.add( tooth ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetOnlyGetter.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetOnlyGetter.java index 787b5e677c..d9d593a230 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetOnlyGetter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetOnlyGetter.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; @@ -33,21 +20,21 @@ public List getPets() { } public void addCat(Long cat) { - // dummy method to test selection mechanims + // dummy method to test selection mechanism } public void addDog(Long cat) { - // dummy method to test selection mechanims + // dummy method to test selection mechanism } public void addPets(Long cat) { - // dummy method to test selection mechanims + // dummy method to test selection mechanism } public void addPet(Long pet) { AdderUsageObserver.setUsed( true ); if ( pets == null ) { - pets = new ArrayList(); + pets = new ArrayList<>(); } pets.add( pet ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetViaTargetType.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetViaTargetType.java index 2d849de1fe..22123aa970 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetViaTargetType.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetViaTargetType.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; @@ -39,7 +26,7 @@ public void setPets(List pets) { public void addPet(IndoorPet pet) { AdderUsageObserver.setUsed( true ); if ( pets == null ) { - pets = new ArrayList(); + pets = new ArrayList<>(); } pets.add( pet ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetWithAnimals.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetWithAnimals.java new file mode 100644 index 0000000000..10770922bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetWithAnimals.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.ap.test.collection.adder._target; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class TargetWithAnimals { + + private List animals = new ArrayList<>(); + + public List getAnimals() { + return animals; + } + + public void setAnimals(List animals) { + this.animals = animals; + } + + public void addAnimal(String animal) { + animals.add( animal ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetWithoutSetter.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetWithoutSetter.java index 527e03b664..61e670c3a7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetWithoutSetter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/TargetWithoutSetter.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder._target; @@ -35,7 +22,7 @@ public List getPets() { public void addPet(Long pet) { AdderUsageObserver.setUsed( true ); if ( pets == null ) { - pets = new ArrayList(); + pets = new ArrayList<>(); } pets.add( pet ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Foo.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Foo.java index 070c4af121..6395243177 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Foo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Foo.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SingleElementSource.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SingleElementSource.java index d956420297..01ab35a91d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SingleElementSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SingleElementSource.java @@ -1,24 +1,10 @@ -/** - * Copyright 2012-2015 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.test.collection.adder.source; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Source.java index 792751049d..862ea96fc3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Source2.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Source2.java index 0da5b6d13b..93369bde2d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Source2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/Source2.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SourceTeeth.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SourceTeeth.java index ca6d1eac08..3abe3afc2e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SourceTeeth.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SourceTeeth.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.adder.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SourceWithPets.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SourceWithPets.java new file mode 100644 index 0000000000..bbb0006532 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SourceWithPets.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.ap.test.collection.adder.source; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class SourceWithPets { + + private List pets; + + public List getPets() { + return pets; + } + + public void setPets(List pets) { + this.pets = pets; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java index 03fc511316..dccfcf01ca 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java @@ -1,26 +1,10 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; -import static org.fest.assertions.Assertions.assertThat; -import static org.fest.assertions.MapAssert.entry; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -36,11 +20,14 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentNavigableMap; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; @WithClasses({ Source.class, @@ -49,10 +36,13 @@ TargetFoo.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class DefaultCollectionImplementationTest { - @Test + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( SourceTargetMapper.class ); + + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForConcurrentMap() { ConcurrentMap target = @@ -61,7 +51,7 @@ public void shouldUseDefaultImplementationForConcurrentMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForConcurrentNavigableMap() { ConcurrentNavigableMap target = @@ -70,7 +60,7 @@ public void shouldUseDefaultImplementationForConcurrentNavigableMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForMap() { Map target = SourceTargetMapper.INSTANCE.sourceFooMapToTargetFooMap( createSourceFooMap() ); @@ -78,7 +68,7 @@ public void shouldUseDefaultImplementationForMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForNavigableMap() { NavigableMap target = @@ -87,7 +77,7 @@ public void shouldUseDefaultImplementationForNavigableMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForSortedMap() { SortedMap target = @@ -96,7 +86,7 @@ public void shouldUseDefaultImplementationForSortedMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForNaviableSet() { NavigableSet target = @@ -105,7 +95,7 @@ public void shouldUseDefaultImplementationForNaviableSet() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForCollection() { Collection target = @@ -114,7 +104,7 @@ public void shouldUseDefaultImplementationForCollection() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForIterable() { Iterable target = @@ -123,7 +113,7 @@ public void shouldUseDefaultImplementationForIterable() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForList() { List target = SourceTargetMapper.INSTANCE.sourceFoosToTargetFoos( createSourceFooList() ); @@ -131,16 +121,16 @@ public void shouldUseDefaultImplementationForList() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForSet() { Set target = - SourceTargetMapper.INSTANCE.sourceFoosToTargetFoos( new HashSet( createSourceFooList() ) ); + SourceTargetMapper.INSTANCE.sourceFoosToTargetFoos( new HashSet<>( createSourceFooList() ) ); assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForSortedSet() { SortedSet target = @@ -149,10 +139,10 @@ public void shouldUseDefaultImplementationForSortedSet() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("19") public void shouldUseTargetParameterForMapping() { - List target = new ArrayList(); + List target = new ArrayList<>(); SourceTargetMapper.INSTANCE.sourceFoosToTargetFoosUsingTargetParameter( target, createSourceFooList() @@ -161,19 +151,33 @@ public void shouldUseTargetParameterForMapping() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("19") public void shouldUseAndReturnTargetParameterForMapping() { - List target = new ArrayList(); + List target = new ArrayList<>(); Iterable result = SourceTargetMapper.INSTANCE .sourceFoosToTargetFoosUsingTargetParameterAndReturn( createSourceFooList(), target ); - assertThat( target == result ).isTrue(); + assertThat( result ).isSameAs( target ); + assertResultList( target ); + } + + @ProcessorTest + @IssueKey("1752") + public void shouldUseAndReturnTargetParameterForNullMapping() { + List target = new ArrayList<>(); + target.add( new TargetFoo( "Bob" ) ); + target.add( new TargetFoo( "Alice" ) ); + Iterable result = + SourceTargetMapper.INSTANCE + .sourceFoosToTargetFoosUsingTargetParameterAndReturn( null, target ); + + assertThat( result ).isSameAs( target ); assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("92") public void shouldUseDefaultImplementationForListWithoutSetter() { Source source = new Source(); @@ -192,11 +196,11 @@ private void assertResultList(Iterable fooIterable) { private void assertResultMap(Map result) { assertThat( result ).isNotNull(); assertThat( result ).hasSize( 2 ); - assertThat( result ).includes( entry( "1", new TargetFoo( "Bob" ) ), entry( "2", new TargetFoo( "Alice" ) ) ); + assertThat( result ).contains( entry( "1", new TargetFoo( "Bob" ) ), entry( "2", new TargetFoo( "Alice" ) ) ); } private Map createSourceFooMap() { - Map map = new HashMap(); + Map map = new HashMap<>(); map.put( 1L, new SourceFoo( "Bob" ) ); map.put( 2L, new SourceFoo( "Alice" ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterCollectionMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterCollectionMappingTest.java index c8214732f9..8723969723 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterCollectionMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterCollectionMappingTest.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.collection.defaultimplementation; @@ -23,30 +10,26 @@ import java.util.List; import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; - -import static org.fest.assertions.Assertions.assertThat; -import static org.fest.assertions.MapAssert.entry; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * @author Andreas Gudian * */ @WithClasses( { NoSetterMapper.class, NoSetterSource.class, NoSetterTarget.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class NoSetterCollectionMappingTest { - @Test + @ProcessorTest @IssueKey( "220" ) public void compilesAndMapsCorrectly() { NoSetterSource source = new NoSetterSource(); source.setListValues( Arrays.asList( "foo", "bar" ) ); - HashMap mapValues = new HashMap(); + HashMap mapValues = new HashMap<>(); mapValues.put( "fooKey", "fooVal" ); mapValues.put( "barKey", "barVal" ); @@ -54,7 +37,7 @@ public void compilesAndMapsCorrectly() { NoSetterTarget target = NoSetterMapper.INSTANCE.toTarget( source ); assertThat( target.getListValues() ).containsExactly( "foo", "bar" ); - assertThat( target.getMapValues() ).includes( entry( "fooKey", "fooVal" ), entry( "barKey", "barVal" ) ); + assertThat( target.getMapValues() ).contains( entry( "fooKey", "fooVal" ), entry( "barKey", "barVal" ) ); // now test existing instances @@ -69,7 +52,7 @@ public void compilesAndMapsCorrectly() { assertThat( target2.getListValues() ).containsExactly( "baz" ); assertThat( target2.getMapValues() ).isSameAs( originalMapInstance ); // source2 mapvalues is empty, so the map is not cleared - assertThat( target2.getMapValues() ).includes( entry( "fooKey", "fooVal" ), entry( "barKey", "barVal" ) ); + //assertThat( target2.getMapValues() ).contains( entry( "fooKey", "fooVal" ), entry( "barKey", "barVal" ) ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterMapper.java index b52a2cbe87..0c7302d657 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterSource.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterSource.java index b0d17e5318..6bb1d429b4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterTarget.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterTarget.java index 9e7c2e32a7..817879fa01 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/NoSetterTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; @@ -28,8 +15,8 @@ * */ public class NoSetterTarget { - private List listValues = new ArrayList(); - private Map mapValues = new HashMap(); + private List listValues = new ArrayList<>(); + private Map mapValues = new HashMap<>(); public List getListValues() { return listValues; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Source.java index 95dbb3c2a8..990681492a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceFoo.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceFoo.java index 487bff6b21..697c27fe45 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceFoo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceFoo.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java index 2b7804a1da..2f0d8085e8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; @@ -39,7 +26,7 @@ public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - @Mapping(source = "fooList", target = "fooListNoSetter") + @Mapping(target = "fooListNoSetter", source = "fooList") Target sourceToTarget(Source source); TargetFoo sourceFooToTargetFoo(SourceFoo sourceFoo); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Target.java index c321b9bf43..713865da62 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; @@ -27,7 +14,7 @@ public class Target { public List getFooListNoSetter() { if ( fooListNoSetter == null ) { - fooListNoSetter = new ArrayList(); + fooListNoSetter = new ArrayList<>(); } return fooListNoSetter; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/TargetFoo.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/TargetFoo.java index 060eab7cc6..5a843b045c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/TargetFoo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/TargetFoo.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.defaultimplementation; @@ -58,14 +45,11 @@ public boolean equals(Object obj) { } TargetFoo other = (TargetFoo) obj; if ( name == null ) { - if ( other.name != null ) { - return false; - } + return other.name == null; } - else if ( !name.equals( other.name ) ) { - return false; + else { + return name.equals( other.name ); } - return true; } @Override diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java new file mode 100644 index 0000000000..4551d5b0b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.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.ap.test.collection.defaultimplementation.jdk21; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.ap.test.collection.defaultimplementation.Source; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.Target; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses({ + Source.class, + Target.class, + SourceFoo.class, + TargetFoo.class, + SequencedCollectionsMapper.class +}) +@IssueKey("3420") +class SequencedCollectionsDefaultImplementationTest { + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedMap() { + SequencedMap target = + SequencedCollectionsMapper.INSTANCE.sourceFooMapToTargetFooSequencedMap( createSourceFooMap() ); + + assertResultMap( target ); + } + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedSet() { + SequencedSet target = + SequencedCollectionsMapper.INSTANCE.sourceFoosToTargetFooSequencedSet( createSourceFooList() ); + + assertResultList( target ); + } + + private void assertResultList(Iterable fooIterable) { + assertThat( fooIterable ).isNotNull(); + assertThat( fooIterable ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); + } + + private void assertResultMap(Map result) { + assertThat( result ).isNotNull(); + assertThat( result ).hasSize( 2 ); + assertThat( result ).contains( entry( "1", new TargetFoo( "Bob" ) ), entry( "2", new TargetFoo( "Alice" ) ) ); + } + + private Map createSourceFooMap() { + Map map = new HashMap<>(); + map.put( 1L, new SourceFoo( "Bob" ) ); + map.put( 2L, new SourceFoo( "Alice" ) ); + + return map; + } + + private List createSourceFooList() { + return Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java new file mode 100644 index 0000000000..bbffb56b0c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.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.ap.test.collection.defaultimplementation.jdk21; + +import java.util.Collection; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SequencedCollectionsMapper { + + SequencedCollectionsMapper INSTANCE = Mappers.getMapper( SequencedCollectionsMapper.class ); + + SequencedSet sourceFoosToTargetFooSequencedSet(Collection foos); + + SequencedMap sourceFooMapToTargetFooSequencedMap(Map foos); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/EmptyItererableMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/EmptyItererableMappingMapper.java index 2ae982ff4f..cc93860cbd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/EmptyItererableMappingMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/EmptyItererableMappingMapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.collection.erroneous; import java.util.Date; import java.util.List; + import org.mapstruct.IterableMapping; import org.mapstruct.Mapper; @@ -30,8 +18,6 @@ @Mapper public interface EmptyItererableMappingMapper { - @IterableMapping List stringListToDateList(List dates); - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/EmptyMapMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/EmptyMapMappingMapper.java index 1995bf4850..14ba978fcc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/EmptyMapMappingMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/EmptyMapMappingMapper.java @@ -1,25 +1,13 @@ -/** - * Copyright 2012-2015 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.test.collection.erroneous; import java.util.Date; import java.util.Map; + import org.mapstruct.MapMapping; import org.mapstruct.Mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionMappingTest.java index 414d364970..d9861be87e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionMappingTest.java @@ -1,62 +1,68 @@ -/** - * Copyright 2012-2015 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.collection.erroneous; import javax.tools.Diagnostic.Kind; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.mapstruct.ap.test.NoProperties; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * Test for illegal mappings between collection types, iterable and non-iterable types etc. * * @author Gunnar Morling */ -@RunWith(AnnotationProcessorTestRunner.class) public class ErroneousCollectionMappingTest { - @Test + @ProcessorTest @IssueKey("6") - @WithClasses({ ErroneousCollectionToNonCollectionMapper.class }) + @WithClasses({ ErroneousCollectionToNonCollectionMapper.class, Source.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = ErroneousCollectionToNonCollectionMapper.class, kind = Kind.ERROR, - line = 28, - messageRegExp = "Can't generate mapping method from iterable type to non-iterable type"), + line = 15, + message = "Can't generate mapping method from iterable type from java stdlib to non-iterable type."), + @Diagnostic(type = ErroneousCollectionToNonCollectionMapper.class, + kind = Kind.ERROR, + line = 17, + message = "Can't generate mapping method from non-iterable type to iterable type from java stdlib."), @Diagnostic(type = ErroneousCollectionToNonCollectionMapper.class, kind = Kind.ERROR, - line = 30, - messageRegExp = "Can't generate mapping method from non-iterable type to iterable type") + line = 19, + message = "Can't generate mapping method from non-iterable type to iterable type from java stdlib.") } ) - public void shouldFailToGenerateImplementationBetweenCollectionAndNonCollection() { + public void shouldFailToGenerateImplementationBetweenCollectionAndNonCollectionWithResultTypeFromJava() { } - @Test + @ProcessorTest + @IssueKey("729") + @WithClasses({ ErroneousCollectionToPrimitivePropertyMapper.class, Source.class, Target.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCollectionToPrimitivePropertyMapper.class, + kind = Kind.ERROR, + line = 13, + message = "Can't map property \"List strings\" to \"int strings\". " + + "Consider to declare/implement a mapping method: \"int map(List value)\".") + } + ) + public void shouldFailToGenerateImplementationBetweenCollectionAndPrimitive() { + } + + @ProcessorTest @IssueKey("417") @WithClasses({ EmptyItererableMappingMapper.class }) @ExpectedCompilationOutcome( @@ -64,15 +70,15 @@ public void shouldFailToGenerateImplementationBetweenCollectionAndNonCollection( diagnostics = { @Diagnostic(type = EmptyItererableMappingMapper.class, kind = Kind.ERROR, - line = 35, - messageRegExp = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + line = 22, + message = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + "undefined in @IterableMapping, define at least one of them.") } ) public void shouldFailOnEmptyIterableAnnotation() { } - @Test + @ProcessorTest @IssueKey("417") @WithClasses({ EmptyMapMappingMapper.class }) @ExpectedCompilationOutcome( @@ -80,8 +86,8 @@ public void shouldFailOnEmptyIterableAnnotation() { diagnostics = { @Diagnostic(type = EmptyMapMappingMapper.class, kind = Kind.ERROR, - line = 34, - messageRegExp = "'nullValueMappingStrategy', 'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', " + line = 22, + message = "'nullValueMappingStrategy', 'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', " + "'valueDateFormat', 'valueQualfiedBy' and 'valueTargetType' are all undefined in @MapMapping, " + "define at least one of them.") } @@ -89,51 +95,102 @@ public void shouldFailOnEmptyIterableAnnotation() { public void shouldFailOnEmptyMapAnnotation() { } - @Test + @ProcessorTest @IssueKey("459") - @WithClasses({ ErroneousCollectionNoElementMappingFound.class }) + @WithClasses({ ErroneousCollectionNoElementMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = ErroneousCollectionNoElementMappingFound.class, kind = Kind.ERROR, - line = 36, - messageRegExp = "No implementation can be generated for this method. Found no method nor implicit " - + "conversion for mapping source element type into target element type.") + line = 25, + message = "No target bean properties found: can't map Collection element " + + "\"WithProperties withProperties\" to \"NoProperties noProperties\". " + + "Consider to declare/implement a mapping method: \"NoProperties map(WithProperties value)\".") } ) public void shouldFailOnNoElementMappingFound() { } - @Test + @ProcessorTest + @IssueKey("993") + @WithClasses({ ErroneousCollectionNoElementMappingFoundDisabledAuto.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCollectionNoElementMappingFoundDisabledAuto.class, + kind = Kind.ERROR, + line = 19, + message = "Can't map collection element \"AttributedString\" to \"String \". " + + "Consider to declare/implement a mapping method: \"String map(AttributedString value)\".") + } + ) + public void shouldFailOnNoElementMappingFoundWithDisabledAuto() { + } + + @ProcessorTest @IssueKey("459") - @WithClasses({ ErroneousCollectionNoKeyMappingFound.class }) + @WithClasses({ ErroneousCollectionNoKeyMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = ErroneousCollectionNoKeyMappingFound.class, kind = Kind.ERROR, - line = 36, - messageRegExp = "No implementation can be generated for this method. Found no method nor implicit " - + "conversion for mapping source key type to target key type.") + line = 25, + message = "No target bean properties found: can't map Map key \"WithProperties withProperties\" to " + + "\"NoProperties noProperties\". " + + "Consider to declare/implement a mapping method: \"NoProperties map(WithProperties value)\".") } ) public void shouldFailOnNoKeyMappingFound() { } - @Test + @ProcessorTest + @IssueKey("993") + @WithClasses({ ErroneousCollectionNoKeyMappingFoundDisabledAuto.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCollectionNoKeyMappingFoundDisabledAuto.class, + kind = Kind.ERROR, + line = 19, + message = "Can't map map key \"AttributedString\" to \"String \". Consider to " + + "declare/implement a mapping method: \"String map(AttributedString value)\".") + } + ) + public void shouldFailOnNoKeyMappingFoundWithDisabledAuto() { + } + + @ProcessorTest @IssueKey("459") - @WithClasses({ ErroneousCollectionNoValueMappingFound.class }) + @WithClasses({ ErroneousCollectionNoValueMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = ErroneousCollectionNoValueMappingFound.class, kind = Kind.ERROR, - line = 36, - messageRegExp = "No implementation can be generated for this method. Found no method nor implicit " - + "conversion for mapping source value type to target value type.") + line = 25, + message = "No target bean properties found: can't map Map value \"WithProperties withProperties\" " + + "to \"NoProperties noProperties\". " + + "Consider to declare/implement a mapping method: \"NoProperties map(WithProperties value)\".") } ) public void shouldFailOnNoValueMappingFound() { } + + @ProcessorTest + @IssueKey("993") + @WithClasses({ ErroneousCollectionNoValueMappingFoundDisabledAuto.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousCollectionNoValueMappingFoundDisabledAuto.class, + kind = Kind.ERROR, + line = 19, + message = "Can't map map value \"AttributedString\" to \"String \". " + + "Consider to declare/implement a mapping method: \"String map(AttributedString value)\".") + } + ) + public void shouldFailOnNoValueMappingFoundWithDisabledAuto() { + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoElementMappingFound.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoElementMappingFound.java index 15ef3a16b2..e3659574ed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoElementMappingFound.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoElementMappingFound.java @@ -1,26 +1,15 @@ -/** - * Copyright 2012-2015 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.test.collection.erroneous; -import java.text.AttributedString; import java.util.List; + import org.mapstruct.Mapper; +import org.mapstruct.ap.test.NoProperties; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.factory.Mappers; /** @@ -33,6 +22,6 @@ public interface ErroneousCollectionNoElementMappingFound { ErroneousCollectionNoElementMappingFound INSTANCE = Mappers.getMapper( ErroneousCollectionNoElementMappingFound.class ); - List map(List source); + List map(List source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoElementMappingFoundDisabledAuto.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoElementMappingFoundDisabledAuto.java new file mode 100644 index 0000000000..aa249cd205 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoElementMappingFoundDisabledAuto.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.ap.test.collection.erroneous; + +import java.text.AttributedString; +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(disableSubMappingMethodsGeneration = true) +public interface ErroneousCollectionNoElementMappingFoundDisabledAuto { + + List map(List source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoKeyMappingFound.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoKeyMappingFound.java index 7ff8aa3ecc..346124d59f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoKeyMappingFound.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoKeyMappingFound.java @@ -1,26 +1,15 @@ -/** - * Copyright 2012-2015 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.test.collection.erroneous; -import java.text.AttributedString; import java.util.Map; + import org.mapstruct.Mapper; +import org.mapstruct.ap.test.NoProperties; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.factory.Mappers; /** @@ -33,6 +22,6 @@ public interface ErroneousCollectionNoKeyMappingFound { ErroneousCollectionNoKeyMappingFound INSTANCE = Mappers.getMapper( ErroneousCollectionNoKeyMappingFound.class ); - Map map(Map source); + Map map(Map source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoKeyMappingFoundDisabledAuto.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoKeyMappingFoundDisabledAuto.java new file mode 100644 index 0000000000..ae966e4f5d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoKeyMappingFoundDisabledAuto.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.ap.test.collection.erroneous; + +import java.text.AttributedString; +import java.util.Map; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(disableSubMappingMethodsGeneration = true) +public interface ErroneousCollectionNoKeyMappingFoundDisabledAuto { + + Map map(Map source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoValueMappingFound.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoValueMappingFound.java index 3d6534b4da..35caf8f60a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoValueMappingFound.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoValueMappingFound.java @@ -1,26 +1,15 @@ -/** - * Copyright 2012-2015 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.test.collection.erroneous; -import java.text.AttributedString; import java.util.Map; + import org.mapstruct.Mapper; +import org.mapstruct.ap.test.NoProperties; +import org.mapstruct.ap.test.WithProperties; import org.mapstruct.factory.Mappers; /** @@ -33,6 +22,6 @@ public interface ErroneousCollectionNoValueMappingFound { ErroneousCollectionNoValueMappingFound INSTANCE = Mappers.getMapper( ErroneousCollectionNoValueMappingFound.class ); - Map map(Map source); + Map map(Map source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoValueMappingFoundDisabledAuto.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoValueMappingFoundDisabledAuto.java new file mode 100644 index 0000000000..220ae0a50c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionNoValueMappingFoundDisabledAuto.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.ap.test.collection.erroneous; + +import java.text.AttributedString; +import java.util.Map; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(disableSubMappingMethodsGeneration = true) +public interface ErroneousCollectionNoValueMappingFoundDisabledAuto { + + Map map(Map source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionToNonCollectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionToNonCollectionMapper.java index 41abefb87d..0df6a4b95c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionToNonCollectionMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionToNonCollectionMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.erroneous; @@ -28,4 +15,6 @@ public interface ErroneousCollectionToNonCollectionMapper { Integer stringSetToInteger(Set strings); Set integerToStringSet(Integer integer); + + Set nonJavaStdlibToCollection(Source stringCollection); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionToPrimitivePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionToPrimitivePropertyMapper.java new file mode 100644 index 0000000000..50a0a4fc0a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionToPrimitivePropertyMapper.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.ap.test.collection.erroneous; + +import org.mapstruct.Mapper; + +@Mapper +public interface ErroneousCollectionToPrimitivePropertyMapper { + + Target mapCollectionToNonCollectionAsProperty(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/Source.java new file mode 100644 index 0000000000..24a9ce883b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/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.ap.test.collection.erroneous; + +import java.util.List; + +public class Source { + + private List strings; + + public List getStrings() { + return strings; + } + + public void setStrings(List strings) { + this.strings = strings; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/Target.java new file mode 100644 index 0000000000..046e92734c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/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.ap.test.collection.erroneous; + +public class Target { + + private int strings; + + public int getStrings() { + return strings; + } + + public void setStrings(int strings) { + this.strings = strings; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Bar.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Bar.java index a730cb62ba..5dfb58c504 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Bar.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Bar.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMapper.java index 8e05e6daaf..39b9d6ce57 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; @@ -27,5 +14,6 @@ public interface CollectionMapper { CollectionMapper INSTANCE = Mappers.getMapper( CollectionMapper.class ); Target sourceToTarget(Source source); + Source targetToSource(Target target); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMapperNullValueMappingReturnDefault.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMapperNullValueMappingReturnDefault.java index 632860d02f..130f281fdb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMapperNullValueMappingReturnDefault.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMapperNullValueMappingReturnDefault.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; @@ -29,5 +16,6 @@ public interface CollectionMapperNullValueMappingReturnDefault { Mappers.getMapper( CollectionMapperNullValueMappingReturnDefault.class ); Target sourceToTarget( Source source ); + Source targetToSource( Target target ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMappingTest.java index 593318d120..67cabc9f95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMappingTest.java @@ -1,41 +1,23 @@ -/** - * Copyright 2012-2015 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.test.collection.forged; - import java.util.Map; - import javax.tools.Diagnostic.Kind; -import org.junit.Test; -import org.junit.runner.RunWith; +import com.google.common.collect.ImmutableMap; import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import com.google.common.collect.ImmutableMap; - -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Test for mappings between collection types, @@ -43,45 +25,52 @@ * @author Sjaak Derksen */ @IssueKey( "4" ) -@RunWith(AnnotationProcessorTestRunner.class) public class CollectionMappingTest { - @Test + @ProcessorTest @WithClasses({ CollectionMapper.class, Source.class, Target.class }) public void shouldForgeNewIterableMappingMethod() { Source source = new Source(); - source.setFooSet( Collections.asSet( "1", "2") ); + source.setFooSet( Collections.asSet( "1", "2" ) ); + source.publicFooSet = Collections.asSet( "3", "4" ); Target target = CollectionMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getFooSet() ).isEqualTo( Collections.asSet( 1L, 2L ) ); + assertThat( target.getPublicFooSet() ).isEqualTo( Collections.asSet( 3L, 4L ) ); Source source2 = CollectionMapper.INSTANCE.targetToSource( target ); assertThat( source2 ).isNotNull(); assertThat( source2.getFooSet() ).isEqualTo( Collections.asSet( "1", "2" ) ); + assertThat( source2.publicFooSet ).isEqualTo( Collections.asSet( "3", "4" ) ); } - @Test + @ProcessorTest @WithClasses({ CollectionMapper.class, Source.class, Target.class }) public void shouldForgeNewMapMappingMethod() { Map sourceMap = ImmutableMap.builder().put( "rabbit", 1L ).build(); Source source = new Source(); source.setBarMap( sourceMap ); + source.publicBarMap = ImmutableMap.builder().put( "fox", 2L ).build(); Target target = CollectionMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); Map targetMap = ImmutableMap.builder().put( "rabbit", "1" ).build(); + Map targetMap2 = ImmutableMap.builder().put( "fox", "2" ).build(); assertThat( target.getBarMap() ).isEqualTo( targetMap ); + assertThat( target.getPublicBarMap() ).isEqualTo( targetMap2 ); Source source2 = CollectionMapper.INSTANCE.targetToSource( target ); assertThat( source2 ).isNotNull(); assertThat( source2.getBarMap() ).isEqualTo( sourceMap ); + assertThat( source2.publicBarMap ).isEqualTo( source.publicBarMap ); } - @Test - @WithClasses({ ErroneousCollectionNonMappableSetMapper.class, + @ProcessorTest + @WithClasses({ + ErroneousCollectionNonMappableSetMapper.class, ErroneousNonMappableSetSource.class, ErroneousNonMappableSetTarget.class, Foo.class, @@ -92,15 +81,16 @@ public void shouldForgeNewMapMappingMethod() { diagnostics = { @Diagnostic(type = ErroneousCollectionNonMappableSetMapper.class, kind = Kind.ERROR, - line = 30, - messageRegExp = "Can't map property \".* nonMappableSet\" to \".* nonMappableSet\". " - + "Consider to declare/implement a mapping method: .*."), + line = 17, + message = "No target bean properties found: " + + "can't map Collection element \"Foo nonMappableSet\" to \"Bar nonMappableSet\". " + + "Consider to declare/implement a mapping method: \"Bar map(Foo value)\"."), } ) public void shouldGenerateNonMappleMethodForSetMapping() { } - @Test + @ProcessorTest @WithClasses({ ErroneousCollectionNonMappableMapMapper.class, ErroneousNonMappableMapSource.class, ErroneousNonMappableMapTarget.class, @@ -112,81 +102,103 @@ public void shouldGenerateNonMappleMethodForSetMapping() { diagnostics = { @Diagnostic(type = ErroneousCollectionNonMappableMapMapper.class, kind = Kind.ERROR, - line = 30, - messageRegExp = "Can't map property \".* nonMappableMap\" to \".* nonMappableMap\". " - + "Consider to declare/implement a mapping method: .*."), + line = 17, + message = "No target bean properties found: " + + "can't map Map key \"Foo nonMappableMap{:key}\" to \"Bar nonMappableMap{:key}\". " + + "Consider to declare/implement a mapping method: \"Bar map(Foo value)\"."), + @Diagnostic(type = ErroneousCollectionNonMappableMapMapper.class, + kind = Kind.ERROR, + line = 17, + message = "No target bean properties found: " + + "can't map Map value \"Foo nonMappableMap{:value}\" to \"Bar nonMappableMap{:value}\". " + + "Consider to declare/implement a mapping method: \"Bar map(Foo value)\"."), } ) public void shouldGenerateNonMappleMethodForMapMapping() { } - - @Test + @ProcessorTest @IssueKey( "640" ) @WithClasses({ CollectionMapper.class, Source.class, Target.class }) public void shouldForgeNewIterableMappingMethodReturnNullOnNullSource() { Source source = new Source(); source.setFooSet( null ); + source.publicFooSet = null; Target target = CollectionMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getFooSet() ).isNull(); + assertThat( target.getPublicFooSet() ).isNull(); Source source2 = CollectionMapper.INSTANCE.targetToSource( target ); assertThat( source2 ).isNotNull(); assertThat( source2.getFooSet() ).isNull(); + assertThat( source2.publicFooSet ).isNull(); } - @Test + @ProcessorTest @IssueKey( "640" ) @WithClasses({ CollectionMapper.class, Source.class, Target.class }) public void shouldForgeNewMapMappingMethodReturnNullOnNullSource() { Source source = new Source(); source.setBarMap( null ); + source.publicBarMap = null; Target target = CollectionMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getBarMap() ).isNull(); + assertThat( target.getPublicBarMap() ).isNull(); Source source2 = CollectionMapper.INSTANCE.targetToSource( target ); assertThat( source2 ).isNotNull(); assertThat( source2.getBarMap() ).isNull(); + assertThat( source2.publicBarMap ).isNull(); } - @Test + @ProcessorTest @IssueKey( "640" ) @WithClasses({ CollectionMapperNullValueMappingReturnDefault.class, Source.class, Target.class }) public void shouldForgeNewIterableMappingMethodReturnEmptyOnNullSource() { Source source = new Source(); source.setFooSet( null ); + source.publicFooSet = null; Target target = CollectionMapperNullValueMappingReturnDefault.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getFooSet() ).isEmpty(); + assertThat( target.getPublicFooSet() ).isEmpty(); + + target.setPublicBarMap( null ); Source source2 = CollectionMapperNullValueMappingReturnDefault.INSTANCE.targetToSource( target ); assertThat( source2 ).isNotNull(); assertThat( source2.getFooSet() ).isEmpty(); + assertThat( source2.publicBarMap ).isEmpty(); } - @Test + @ProcessorTest @IssueKey( "640" ) @WithClasses({ CollectionMapperNullValueMappingReturnDefault.class, Source.class, Target.class }) public void shouldForgeNewMapMappingMethodReturnEmptyOnNullSource() { Source source = new Source(); source.setBarMap( null ); + source.publicBarMap = null; Target target = CollectionMapperNullValueMappingReturnDefault.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getBarMap() ).isEmpty(); + assertThat( target.getPublicBarMap() ).isEmpty(); + + target.setPublicBarMap( null ); Source source2 = CollectionMapperNullValueMappingReturnDefault.INSTANCE.targetToSource( target ); assertThat( source2 ).isNotNull(); assertThat( source2.getBarMap() ).isEmpty(); + assertThat( source2.publicBarMap ).isEmpty(); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousCollectionNonMappableMapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousCollectionNonMappableMapMapper.java index 869abe34d9..bb77390281 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousCollectionNonMappableMapMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousCollectionNonMappableMapMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousCollectionNonMappableSetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousCollectionNonMappableSetMapper.java index 4f2a899dc0..94fbed368a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousCollectionNonMappableSetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousCollectionNonMappableSetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableMapSource.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableMapSource.java index 3484365b7b..c00c64f253 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableMapSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableMapSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; @@ -31,6 +18,4 @@ public Map getNonMappableMap() { public void setNonMappableMap(Map nonMappableMap) { this.nonMappableMap = nonMappableMap; } - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableMapTarget.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableMapTarget.java index d3842dcdba..cae62b65a6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableMapTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableMapTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableSetSource.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableSetSource.java index 5f73be4e22..8a736cda11 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableSetSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableSetSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableSetTarget.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableSetTarget.java index 6aa7f253b5..701c829112 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableSetTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/ErroneousNonMappableSetTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Foo.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Foo.java index 65f548e31c..eb4485ab17 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Foo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Foo.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Source.java index 1aaab51991..e98dcc37b5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; @@ -23,9 +10,15 @@ public class Source { + //CHECKSTYLE:OFF + public Set publicFooSet; + //CHECKSTYLE:ON private Set fooSet; private Set fooSet2; + //CHECKSTYLE:OFF + public Map publicBarMap; + //CHECKSTYLE:ON private Map barMap; private Map barMap2; @@ -60,8 +53,4 @@ public Map getBarMap2() { public void setBarMap2( Map barMap2 ) { this.barMap2 = barMap2; } - - - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Target.java index f989836680..4a291e5584 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.forged; @@ -28,6 +15,9 @@ public class Target { private Map barMap; private Map barMap2; + private Set publicFooSet; + private Map publicBarMap; + public Set getFooSet() { return fooSet; } @@ -60,5 +50,19 @@ public void setBarMap2( Map barMap2 ) { this.barMap2 = barMap2; } + public Set getPublicFooSet() { + return publicFooSet; + } + public void setPublicFooSet(Set publicFooSet) { + this.publicFooSet = publicFooSet; + } + + public Map getPublicBarMap() { + return publicBarMap; + } + + public void setPublicBarMap(Map publicBarMap) { + this.publicBarMap = publicBarMap; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardDto.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardDto.java new file mode 100644 index 0000000000..917ed483ff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardDto.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.ap.test.collection.immutabletarget; + +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class CupboardDto { + + private List content; + + public List getContent() { + return content; + } + + public void setContent(List content) { + this.content = content; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntity.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntity.java new file mode 100644 index 0000000000..87e9661780 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntity.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.ap.test.collection.immutabletarget; + +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class CupboardEntity { + + private List content; + + public List getContent() { + return content; + } + + public void setContent(List content) { + this.content = content; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.java new file mode 100644 index 0000000000..1f2f2f00b7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardEntityOnlyGetter.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.ap.test.collection.immutabletarget; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author Sjaak Derksen + */ +public class CupboardEntityOnlyGetter { + + private List content = new ArrayList<>(); + + public List getContent() { + return content; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardMapper.java new file mode 100644 index 0000000000..b00f4441c5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardMapper.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.ap.test.collection.immutabletarget; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE ) +public interface CupboardMapper { + + CupboardMapper INSTANCE = Mappers.getMapper( CupboardMapper.class ); + + void map( CupboardDto in, @MappingTarget CupboardEntity out ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.java new file mode 100644 index 0000000000..a6674e629f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/CupboardNoSetterMapper.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.ap.test.collection.immutabletarget; + +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE ) +public interface CupboardNoSetterMapper { + + CupboardNoSetterMapper INSTANCE = Mappers.getMapper( CupboardNoSetterMapper.class ); + + void map( CupboardDto in, @MappingTarget CupboardEntityOnlyGetter out ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java new file mode 100644 index 0000000000..8097dfdfe9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ImmutableProductTest.java @@ -0,0 +1,64 @@ +/* + * 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.test.collection.immutabletarget; + +import java.util.Arrays; +import java.util.Collections; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Sjaak Derksen + */ +@WithClasses({CupboardDto.class, CupboardEntity.class, CupboardMapper.class}) +@IssueKey( "1126" ) +public class ImmutableProductTest { + + @ProcessorTest + public void shouldHandleImmutableTarget() { + + CupboardDto in = new CupboardDto(); + in.setContent( Arrays.asList( "cups", "soucers" ) ); + CupboardEntity out = new CupboardEntity(); + out.setContent( Collections.emptyList() ); + + CupboardMapper.INSTANCE.map( in, out ); + + assertThat( out.getContent() ).isNotNull(); + assertThat( out.getContent() ).containsExactly( "cups", "soucers" ); + } + + @ProcessorTest + @WithClasses({ + CupboardNoSetterMapper.class, + CupboardEntityOnlyGetter.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = CupboardNoSetterMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22, + message = "No target property found for target \"CupboardEntityOnlyGetter\"."), + }) + public void shouldIgnoreImmutableTarget() { + CupboardDto in = new CupboardDto(); + in.setContent( Arrays.asList( "flour", "peas" ) ); + CupboardEntityOnlyGetter out = new CupboardEntityOnlyGetter(); + out.getContent().add( "bread" ); + CupboardNoSetterMapper.INSTANCE.map( in, out ); + + assertThat( out.getContent() ).containsExactly( "bread" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.java new file mode 100644 index 0000000000..c989aa4e19 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/Fruit.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.ap.test.collection.iterabletolist; + +/** + * + * @author Xiu-Hong Kooi + */ +public class Fruit { + + private String type; + + public Fruit(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.java new file mode 100644 index 0000000000..6a04121044 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitSalad.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.ap.test.collection.iterabletolist; + +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitSalad { + + private Iterable fruits; + + public FruitSalad(List fruits) { + this.fruits = fruits; + } + + public Iterable getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.java new file mode 100644 index 0000000000..bb4fdc66af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMapper.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.ap.test.collection.iterabletolist; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Xiu-Hong Kooi + */ +@Mapper +public interface FruitsMapper { + + FruitsMapper INSTANCE = Mappers.getMapper( + FruitsMapper.class ); + + FruitsMenu fruitSaladToMenu(FruitSalad salad); + + FruitSalad fruitsMenuToSalad(FruitsMenu menu); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.java new file mode 100644 index 0000000000..6f272dc454 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/FruitsMenu.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.test.collection.iterabletolist; + +import java.util.Iterator; +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitsMenu implements Iterable { + + private List fruits; + + public FruitsMenu(List fruits) { + this.fruits = fruits; + } + + public List getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } + + @Override + public Iterator iterator() { + return this.fruits.iterator(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java new file mode 100644 index 0000000000..81a640499b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletolist/IterableToListMappingTest.java @@ -0,0 +1,47 @@ +/* + * 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.test.collection.iterabletolist; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Xiu-Hong Kooi + */ +@WithClasses({ FruitsMenu.class, FruitSalad.class, Fruit.class, FruitsMapper.class }) +public class IterableToListMappingTest { + + @ProcessorTest + @IssueKey("3376") + public void shouldMapIterableToList() { + List fruits = Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ); + FruitsMenu menu = new FruitsMenu(fruits); + FruitSalad salad = FruitsMapper.INSTANCE.fruitsMenuToSalad( menu ); + Iterator itr = salad.getFruits().iterator(); + assertThat( itr.next().getType() ).isEqualTo( "mango" ); + assertThat( itr.next().getType() ).isEqualTo( "apple" ); + assertThat( itr.next().getType() ).isEqualTo( "banana" ); + } + + @ProcessorTest + @IssueKey("3376") + public void shouldMapListToIterable() { + List fruits = Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ); + FruitSalad salad = new FruitSalad(fruits); + FruitsMenu menu = FruitsMapper.INSTANCE.fruitSaladToMenu( salad ); + assertThat( menu.getFruits() ).extracting( Fruit::getType ).containsExactly( "mango", "apple", "banana" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Fruit.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Fruit.java new file mode 100644 index 0000000000..f012eb5a84 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Fruit.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.ap.test.collection.iterabletononiterable; + +/** + * + * @author Saheb Preet Singh + */ +public class Fruit { + + private String type; + + public Fruit(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitSalad.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitSalad.java new file mode 100644 index 0000000000..a6298a6095 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitSalad.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.ap.test.collection.iterabletononiterable; + +import java.util.List; + +/** + * + * @author Saheb Preet Singh + */ +public class FruitSalad { + + private List fruits; + + public FruitSalad(List fruits) { + this.fruits = fruits; + } + + public List getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitsMapper.java new file mode 100644 index 0000000000..3d9cc94bad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitsMapper.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.ap.test.collection.iterabletononiterable; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Saheb Preet Singh + */ +@Mapper +public interface FruitsMapper { + + FruitsMapper INSTANCE = Mappers.getMapper( + FruitsMapper.class ); + + FruitsMenu fruitSaladToMenu(FruitSalad salad); + + FruitSalad fruitsMenuToSalad(FruitsMenu menu); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitsMenu.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitsMenu.java new file mode 100644 index 0000000000..0b1ef94ded --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/FruitsMenu.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.test.collection.iterabletononiterable; + +import java.util.Iterator; +import java.util.List; + +/** + * + * @author Saheb Preet Singh + */ +public class FruitsMenu implements Iterable { + + private List fruits; + + public FruitsMenu(List fruits) { + this.fruits = fruits; + } + + public List getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } + + @Override + public Iterator iterator() { + return this.fruits.iterator(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/IterableToNonIterableMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/IterableToNonIterableMappingTest.java index 85f97d6b2e..7467387d8b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/IterableToNonIterableMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/IterableToNonIterableMappingTest.java @@ -1,57 +1,69 @@ -/** - * Copyright 2012-2015 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.collection.iterabletononiterable; -import static org.fest.assertions.Assertions.assertThat; - import java.util.Arrays; +import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -@WithClasses({ Source.class, Target.class, SourceTargetMapper.class, StringListMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class, StringListMapper.class, FruitsMenu.class, + FruitSalad.class, Fruit.class, FruitsMapper.class }) public class IterableToNonIterableMappingTest { - @Test + @ProcessorTest @IssueKey("6") public void shouldMapStringListToStringUsingCustomMapper() { Source source = new Source(); source.setNames( Arrays.asList( "Alice", "Bob", "Jim" ) ); + source.publicNames = Arrays.asList( "Alice", "Bob", "Jim" ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getNames() ).isEqualTo( "Alice-Bob-Jim" ); + assertThat( target.publicNames ).isEqualTo( "Alice-Bob-Jim" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapStringListToStringUsingCustomMapper() { Target target = new Target(); target.setNames( "Alice-Bob-Jim" ); + target.publicNames = "Alice-Bob-Jim"; Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( source ).isNotNull(); assertThat( source.getNames() ).isEqualTo( Arrays.asList( "Alice", "Bob", "Jim" ) ); + assertThat( source.publicNames ).isEqualTo( Arrays.asList( "Alice", "Bob", "Jim" ) ); + } + + @ProcessorTest + @IssueKey("607") + public void shouldMapIterableToNonIterable() { + List fruits = Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ); + FruitsMenu menu = new FruitsMenu(fruits); + FruitSalad salad = FruitsMapper.INSTANCE.fruitsMenuToSalad( menu ); + assertThat( salad.getFruits().get( 0 ).getType() ).isEqualTo( "mango" ); + assertThat( salad.getFruits().get( 1 ).getType() ).isEqualTo( "apple" ); + assertThat( salad.getFruits().get( 2 ).getType() ).isEqualTo( "banana" ); + } + + @ProcessorTest + @IssueKey("607") + public void shouldMapNonIterableToIterable() { + List fruits = Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ); + FruitSalad salad = new FruitSalad(fruits); + FruitsMenu menu = FruitsMapper.INSTANCE.fruitSaladToMenu( salad ); + assertThat( salad.getFruits() ).extracting( Fruit::getType ).containsExactly( "mango", "apple", "banana" ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Source.java index b324b49f65..4a757d814f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.iterabletononiterable; @@ -22,6 +9,10 @@ public class Source { + //CHECKSTYLE:OFF + public List publicNames; + //CHECKSTYLE:ON + private List names; public List getNames() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/SourceTargetMapper.java index 1ae87ebea8..7395192ac3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.iterabletononiterable; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/StringListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/StringListMapper.java index 190980bfa7..93983acf55 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/StringListMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/StringListMapper.java @@ -1,32 +1,17 @@ -/** - * Copyright 2012-2015 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.collection.iterabletononiterable; import java.util.Arrays; import java.util.List; -import com.google.common.base.Joiner; - public class StringListMapper { public String stringListToString(List strings) { - return strings == null ? null : Joiner.on( "-" ).join( strings ); + return strings == null ? null : String.join( "-", strings ); } public List stringToStringList(String string) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Target.java index d145f07a4e..07cf6a0f85 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/Target.java @@ -1,25 +1,16 @@ -/** - * Copyright 2012-2015 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.collection.iterabletononiterable; public class Target { + //CHECKSTYLE:OFF + public String publicNames; + //CHECKSTYLE:ON + private String names; public String getNames() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.java new file mode 100644 index 0000000000..a15b344908 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/Fruit.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.ap.test.collection.iterabletoset; + +/** + * + * @author Xiu-Hong Kooi + */ +public class Fruit { + + private String type; + + public Fruit(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.java new file mode 100644 index 0000000000..b92ac3160c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitSalad.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.ap.test.collection.iterabletoset; + +import java.util.List; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitSalad { + + private Iterable fruits; + + public FruitSalad(Iterable fruits) { + this.fruits = fruits; + } + + public Iterable getFruits() { + return fruits; + } + + public void setFruits(List fruits) { + this.fruits = fruits; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.java new file mode 100644 index 0000000000..413dd73617 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMapper.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.ap.test.collection.iterabletoset; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Xiu-Hong Kooi + */ +@Mapper +public interface FruitsMapper { + + FruitsMapper INSTANCE = Mappers.getMapper( + FruitsMapper.class ); + + FruitsMenu fruitSaladToMenu(FruitSalad salad); + + FruitSalad fruitsMenuToSalad(FruitsMenu menu); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.java new file mode 100644 index 0000000000..8ae4b8b632 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/FruitsMenu.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.test.collection.iterabletoset; + +import java.util.Iterator; +import java.util.Set; + +/** + * + * @author Xiu-Hong Kooi + */ +public class FruitsMenu implements Iterable { + + private Set fruits; + + public FruitsMenu(Set fruits) { + this.fruits = fruits; + } + + public Set getFruits() { + return fruits; + } + + public void setFruits(Set fruits) { + this.fruits = fruits; + } + + @Override + public Iterator iterator() { + return this.fruits.iterator(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.java new file mode 100644 index 0000000000..0b967503bb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletoset/IterableToSetMappingTest.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.ap.test.collection.iterabletoset; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.stream.Collectors; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Xiu-Hong Kooi + */ +@WithClasses({ FruitsMenu.class, FruitSalad.class, Fruit.class, FruitsMapper.class }) +public class IterableToSetMappingTest { + + @ProcessorTest + @IssueKey("3376") + public void shouldMapIterableToSet() { + Set fruits = new HashSet<>( Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ) ); + FruitsMenu menu = new FruitsMenu(fruits); + FruitSalad salad = FruitsMapper.INSTANCE.fruitsMenuToSalad( menu ); + Iterator itr = salad.getFruits().iterator(); + Set fruitTypes = fruits.stream().map( Fruit::getType ).collect( Collectors.toSet() ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + assertThat( fruitTypes.contains( itr.next().getType() ) ); + } + + @ProcessorTest + @IssueKey("3376") + public void shouldMapSetToIterable() { + Set fruits = new HashSet<>( Arrays.asList( new Fruit( "mango" ), new Fruit( "apple" ), + new Fruit( "banana" ) ) ); + FruitSalad salad = new FruitSalad(fruits); + FruitsMenu menu = FruitsMapper.INSTANCE.fruitSaladToMenu( salad ); + assertThat( menu.getFruits() ).extracting( Fruit::getType ).contains( "mango", "apple", "banana" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/CustomNumberMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/CustomNumberMapper.java index 3a63e7d54f..5fa4de9860 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/CustomNumberMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/CustomNumberMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.map; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java index 9b82ef7835..1261525d4f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java @@ -1,38 +1,24 @@ -/** - * Copyright 2012-2015 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.test.collection.map; +import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.test.collection.map.other.ImportedType; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; - -import static org.fest.assertions.Assertions.assertThat; -import static org.fest.assertions.MapAssert.entry; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * Test for implementation of {@code Map} mapping methods. @@ -41,26 +27,26 @@ */ @WithClasses({ SourceTargetMapper.class, CustomNumberMapper.class, Source.class, Target.class, ImportedType.class }) @IssueKey("44") -@RunWith(AnnotationProcessorTestRunner.class) +@DefaultTimeZone("UTC") public class MapMappingTest { - @Test + @ProcessorTest public void shouldCreateMapMethodImplementation() { - Map values = new HashMap(); - values.put( 42L, new GregorianCalendar( 1980, 0, 1 ).getTime() ); - values.put( 121L, new GregorianCalendar( 2013, 6, 20 ).getTime() ); + Map values = new HashMap<>(); + values.put( 42L, new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() ); + values.put( 121L, new GregorianCalendar( 2013, Calendar.JULY, 20 ).getTime() ); Map target = SourceTargetMapper.INSTANCE.longDateMapToStringStringMap( values ); assertThat( target ).isNotNull(); assertThat( target ).hasSize( 2 ); - assertThat( target ).includes( + assertThat( target ).contains( entry( "42", "01.01.1980" ), entry( "121", "20.07.2013" ) ); } - @Test + @ProcessorTest public void shouldCreateReverseMapMethodImplementation() { Map values = createStringStringMap(); @@ -69,26 +55,26 @@ public void shouldCreateReverseMapMethodImplementation() { assertResult( target ); } - @Test + @ProcessorTest @IssueKey("19") public void shouldCreateMapMethodImplementationWithTargetParameter() { Map values = createStringStringMap(); - Map target = new HashMap(); - target.put( 66L, new GregorianCalendar( 2013, 7, 16 ).getTime() ); + Map target = new HashMap<>(); + target.put( 66L, new GregorianCalendar( 2013, Calendar.AUGUST, 16 ).getTime() ); SourceTargetMapper.INSTANCE.stringStringMapToLongDateMapUsingTargetParameter( target, values ); assertResult( target ); } - @Test + @ProcessorTest @IssueKey("19") public void shouldCreateMapMethodImplementationWithReturnedTargetParameter() { Map values = createStringStringMap(); - Map target = new HashMap(); - target.put( 66L, new GregorianCalendar( 2013, 7, 16 ).getTime() ); + Map target = new HashMap<>(); + target.put( 66L, new GregorianCalendar( 2013, Calendar.AUGUST, 16 ).getTime() ); Map returnedTarget = SourceTargetMapper.INSTANCE .stringStringMapToLongDateMapUsingTargetParameterAndReturn( values, target ); @@ -98,68 +84,100 @@ public void shouldCreateMapMethodImplementationWithReturnedTargetParameter() { assertResult( target ); } + @ProcessorTest + @IssueKey("1752") + public void shouldCreateMapMethodImplementationWithReturnedTargetParameterAndNullSource() { + Map target = new HashMap<>(); + target.put( 42L, new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() ); + target.put( 121L, new GregorianCalendar( 2013, Calendar.JULY, 20 ).getTime() ); + + Map returnedTarget = SourceTargetMapper.INSTANCE + .stringStringMapToLongDateMapUsingTargetParameterAndReturn( null, target ); + + assertThat( target ).isSameAs( returnedTarget ); + assertResult( target ); + } + private void assertResult(Map target) { assertThat( target ).isNotNull(); assertThat( target ).hasSize( 2 ); - assertThat( target ).includes( - entry( 42L, new GregorianCalendar( 1980, 0, 1 ).getTime() ), - entry( 121L, new GregorianCalendar( 2013, 6, 20 ).getTime() ) + assertThat( target ).contains( + entry( 42L, new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() ), + entry( 121L, new GregorianCalendar( 2013, Calendar.JULY, 20 ).getTime() ) ); } private Map createStringStringMap() { - Map values = new HashMap(); + Map values = new HashMap<>(); values.put( "42", "01.01.1980" ); values.put( "121", "20.07.2013" ); return values; } - @Test + @ProcessorTest public void shouldInvokeMapMethodImplementationForMapTypedProperty() { - Map values = new HashMap(); - values.put( 42L, new GregorianCalendar( 1980, 0, 1 ).getTime() ); - values.put( 121L, new GregorianCalendar( 2013, 6, 20 ).getTime() ); + Map values = new HashMap<>(); + values.put( 42L, new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() ); + values.put( 121L, new GregorianCalendar( 2013, Calendar.JULY, 20 ).getTime() ); Source source = new Source(); source.setValues( values ); + source.setPublicValues( new HashMap<>( values ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getValues() ).isNotNull(); assertThat( target.getValues() ).hasSize( 2 ); - assertThat( target.getValues() ).includes( + assertThat( target.getValues() ).contains( entry( "42", "01.01.1980" ), entry( "121", "20.07.2013" ) ); + + assertThat( target.publicValues ) + .isNotNull() + .hasSize( 2 ) + .contains( + entry( "42", "01.01.1980" ), + entry( "121", "20.07.2013" ) + ); } - @Test + @ProcessorTest public void shouldInvokeReverseMapMethodImplementationForMapTypedProperty() { Map values = createStringStringMap(); Target target = new Target(); target.setValues( values ); + target.publicValues = new HashMap<>( values ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( source ).isNotNull(); assertThat( source.getValues() ).isNotNull(); assertThat( source.getValues() ).hasSize( 2 ); - assertThat( source.getValues() ).includes( - entry( 42L, new GregorianCalendar( 1980, 0, 1 ).getTime() ), - entry( 121L, new GregorianCalendar( 2013, 6, 20 ).getTime() ) + assertThat( source.getValues() ).contains( + entry( 42L, new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() ), + entry( 121L, new GregorianCalendar( 2013, Calendar.JULY, 20 ).getTime() ) ); + + assertThat( source.getPublicValues() ) + .isNotNull() + .hasSize( 2 ) + .contains( + entry( 42L, new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() ), + entry( 121L, new GregorianCalendar( 2013, Calendar.JULY, 20 ).getTime() ) + ); } private Map createIntIntMap() { - Map values = new HashMap(); + Map values = new HashMap<>(); values.put( 42, 47 ); values.put( 121, 123 ); return values; } - @Test + @ProcessorTest @IssueKey("87") public void shouldCreateMapMethodImplementationWithoutConversionOrElementMappingMethod() { Map values = createIntIntMap(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/Source.java index e8ff64b3c6..997e4a8be9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.map; @@ -26,6 +13,8 @@ public class Source { + private Map publicValues; + private Map values; private LinkedHashMap stringEnumMap; @@ -44,4 +33,12 @@ public LinkedHashMap getStringEnumMap() { public void setStringEnumMap(LinkedHashMap stringEnumMap) { this.stringEnumMap = stringEnumMap; } + + public Map getPublicValues() { + return publicValues; + } + + public void setPublicValues(Map publicValues) { + this.publicValues = publicValues; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java index be29325a28..1f996b836e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java @@ -1,30 +1,17 @@ -/** - * Copyright 2012-2015 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.collection.map; import java.util.Date; import java.util.Map; +import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.MappingTarget; -import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.factory.Mappers; @Mapper(uses = CustomNumberMapper.class) @@ -34,6 +21,7 @@ public interface SourceTargetMapper { @MapMapping(valueDateFormat = "dd.MM.yyyy") Map longDateMapToStringStringMap(Map source); + @InheritInverseConfiguration Map stringStringMapToLongDateMap(Map source); @@ -46,6 +34,7 @@ Map stringStringMapToLongDateMapUsingTargetParameterAndReturn(Map target); Target sourceToTarget(Source source); + @InheritInverseConfiguration Source targetToSource(Target target); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/Target.java index 01b92d922a..db1eee8976 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.map; @@ -24,6 +11,9 @@ import org.mapstruct.ap.test.collection.map.other.ImportedType; public class Target { + //CHECKSTYLE:OFF + public Map publicValues; + //CHECKSTYLE:ON private Map values; private LinkedHashMap stringEnumMap; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/other/ImportedType.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/other/ImportedType.java index cc21ccef28..91986ca5c0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/other/ImportedType.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/other/ImportedType.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.map.other; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/BeanMapper.java index f81c2dd657..42a52eb233 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/BeanMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/BeanMapper.java @@ -1,26 +1,14 @@ -/** - * Copyright 2012-2015 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.test.collection.wildcard; import java.math.BigDecimal; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; + import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -40,7 +28,7 @@ BigDecimal map(JAXBElement value) { } JAXBElement map(BigDecimal value) { - return new JAXBElement( new QName( "test" ), BigDecimal.class, value ); + return new JAXBElement<>( new QName( "test" ), BigDecimal.class, value ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/CunningPlan.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/CunningPlan.java index d39ba55bcb..b1a901e0f3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/CunningPlan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/CunningPlan.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; @@ -26,8 +13,6 @@ * @author Sjaak Derksen */ public class CunningPlan { - - private BigDecimal content; private JAXBElement description; @@ -46,5 +31,4 @@ public JAXBElement getDescription() { public void setDescription(JAXBElement description) { this.description = description; } - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableExtendsBoundTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableExtendsBoundTargetMapper.java new file mode 100644 index 0000000000..236d24d77b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableExtendsBoundTargetMapper.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.ap.test.collection.wildcard; + +import java.math.BigDecimal; +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ErroneousIterableExtendsBoundTargetMapper { + + List map(List in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableSuperBoundSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableSuperBoundSourceMapper.java new file mode 100644 index 0000000000..aa29529df9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableSuperBoundSourceMapper.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.ap.test.collection.wildcard; + +import java.math.BigDecimal; +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ErroneousIterableSuperBoundSourceMapper { + + List map(List in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableTypeVarBoundMapperOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableTypeVarBoundMapperOnMapper.java new file mode 100644 index 0000000000..fbe16cb8e9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableTypeVarBoundMapperOnMapper.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.ap.test.collection.wildcard; + +import java.math.BigDecimal; +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ErroneousIterableTypeVarBoundMapperOnMapper { + + List map(List in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableTypeVarBoundMapperOnMethod.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableTypeVarBoundMapperOnMethod.java new file mode 100644 index 0000000000..d725358997 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ErroneousIterableTypeVarBoundMapperOnMethod.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.ap.test.collection.wildcard; + +import java.math.BigDecimal; +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ErroneousIterableTypeVarBoundMapperOnMethod { + + List map(List in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ExtendsBoundSource.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ExtendsBoundSource.java index 76b10a9eb3..8181344d2b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ExtendsBoundSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ExtendsBoundSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ExtendsBoundSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ExtendsBoundSourceTargetMapper.java index d2083eeb44..937cc1f49f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ExtendsBoundSourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/ExtendsBoundSourceTargetMapper.java @@ -1,27 +1,13 @@ -/** - * Copyright 2012-2015 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.test.collection.wildcard; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; - /** * * @author Sjaak Derksen diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/GoodIdea.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/GoodIdea.java index fe5db039ac..9b509e2d10 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/GoodIdea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/GoodIdea.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java index 40f964ec23..4e21b223db 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; @@ -24,4 +11,13 @@ */ public class Idea { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableExtendsBoundTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableExtendsBoundTargetMapper.java deleted file mode 100644 index 9f84424ac1..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableExtendsBoundTargetMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.collection.wildcard; - -import java.math.BigDecimal; -import java.util.List; -import org.mapstruct.Mapper; - -/** - * - * @author Sjaak Derksen - */ -@Mapper -public interface IterableExtendsBoundTargetMapper { - - List map(List in); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableSuperBoundSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableSuperBoundSourceMapper.java deleted file mode 100644 index 19e32d32ce..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableSuperBoundSourceMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.collection.wildcard; - -import java.math.BigDecimal; -import java.util.List; -import org.mapstruct.Mapper; - -/** - * - * @author Sjaak Derksen - */ -@Mapper -public interface IterableSuperBoundSourceMapper { - - List map(List in); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableTypeVarBoundMapperOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableTypeVarBoundMapperOnMapper.java deleted file mode 100644 index ec6942e63d..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableTypeVarBoundMapperOnMapper.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.collection.wildcard; - -import java.math.BigDecimal; -import java.util.List; -import org.mapstruct.Mapper; - -/** - * - * @author Sjaak Derksen - */ -@Mapper -public interface IterableTypeVarBoundMapperOnMapper { - - List map(List in); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableTypeVarBoundMapperOnMethod.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableTypeVarBoundMapperOnMethod.java deleted file mode 100644 index 91ddf35cf6..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/IterableTypeVarBoundMapperOnMethod.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.collection.wildcard; - -import java.math.BigDecimal; -import java.util.List; -import org.mapstruct.Mapper; - -/** - * - * @author Sjaak Derksen - */ -@Mapper -public interface IterableTypeVarBoundMapperOnMethod { - - List map(List in); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java index 27363d465c..00db02170c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; @@ -24,4 +11,13 @@ */ public class Plan { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Source.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Source.java index d0c301e136..1542c7ce96 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/SourceSuperBoundTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/SourceSuperBoundTargetMapper.java index f76a352906..b5bce93345 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/SourceSuperBoundTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/SourceSuperBoundTargetMapper.java @@ -1,27 +1,13 @@ -/** - * Copyright 2012-2015 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.test.collection.wildcard; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; - /** * * @author Sjaak Derksen diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/SuperBoundTarget.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/SuperBoundTarget.java index 6cb7060ec0..7b09752394 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/SuperBoundTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/SuperBoundTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; @@ -45,6 +32,4 @@ public void setElements(List elements) { public void setEntries(Map entries) { this.entries = entries; } - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Target.java index 8a30cec6cf..86b4f001e4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; @@ -45,6 +32,4 @@ public Map getEntries() { public void setEntries(Map entries) { this.entries = entries; } - - } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java index 6eaaaa6787..4da6f8648a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java @@ -1,35 +1,25 @@ -/** - * Copyright 2012-2015 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.collection.wildcard; import java.math.BigDecimal; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; -import static org.fest.assertions.Assertions.assertThat; -import org.junit.Test; -import org.junit.runner.RunWith; + +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxJaxb; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/527. @@ -37,10 +27,12 @@ * @author Sjaak Derksen */ @IssueKey("527") -@RunWith(AnnotationProcessorTestRunner.class) public class WildCardTest { - @Test + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest @WithClasses({ ExtendsBoundSourceTargetMapper.class, ExtendsBoundSource.class, @@ -56,10 +48,13 @@ public void shouldGenerateExtendsBoundSourceForgedIterableMethod() { assertThat( target ).isNotNull(); assertThat( target.getElements() ).isNull(); - + generatedSource.forMapper( ExtendsBoundSourceTargetMapper.class ) + .content() + .as( "Should not contain FQN after extends" ) + .doesNotContain( "? extends org.mapstruct.ap.test.collection.wildcard.Idea" ); } - @Test + @ProcessorTest @WithClasses({ SourceSuperBoundTargetMapper.class, Source.class, @@ -75,72 +70,75 @@ public void shouldGenerateSuperBoundTargetForgedIterableMethod() { assertThat( target ).isNotNull(); assertThat( target.getElements() ).isNull(); - + generatedSource.forMapper( SourceSuperBoundTargetMapper.class ) + .content() + .as( "Should not contain FQN after super" ) + .doesNotContain( "? super org.mapstruct.ap.test.collection.wildcard.Idea" ); } - @Test - @WithClasses({ IterableSuperBoundSourceMapper.class }) + @ProcessorTest + @WithClasses({ ErroneousIterableSuperBoundSourceMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic( type = IterableSuperBoundSourceMapper.class, + @Diagnostic( type = ErroneousIterableSuperBoundSourceMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 32, - messageRegExp = "Can't generate mapping method for a wildcard super bound source." ) + line = 20, + message = "Can't generate mapping method for a wildcard super bound source." ) } ) public void shouldFailOnSuperBoundSource() { } - @Test - @WithClasses({ IterableExtendsBoundTargetMapper.class }) + @ProcessorTest + @WithClasses({ ErroneousIterableExtendsBoundTargetMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic( type = IterableExtendsBoundTargetMapper.class, + @Diagnostic( type = ErroneousIterableExtendsBoundTargetMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 32, - messageRegExp = "Can't generate mapping method for a wildcard extends bound result." ) + line = 20, + message = "Can't generate mapping method for a wildcard extends bound result." ) } ) public void shouldFailOnExtendsBoundTarget() { } - @Test - @WithClasses({ IterableTypeVarBoundMapperOnMethod.class }) + @ProcessorTest + @WithClasses({ ErroneousIterableTypeVarBoundMapperOnMethod.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic( type = IterableTypeVarBoundMapperOnMethod.class, + @Diagnostic(type = ErroneousIterableTypeVarBoundMapperOnMethod.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 32, - messageRegExp = "Can't generate mapping method for a generic type variable target." ) + line = 20, + message = "Can't generate mapping method for a generic type variable target." ) } ) public void shouldFailOnTypeVarSource() { } - @Test - @WithClasses({ IterableTypeVarBoundMapperOnMapper.class }) + @ProcessorTest + @WithClasses({ ErroneousIterableTypeVarBoundMapperOnMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic( type = IterableTypeVarBoundMapperOnMapper.class, + @Diagnostic( type = ErroneousIterableTypeVarBoundMapperOnMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 32, - messageRegExp = "Can't generate mapping method for a generic type variable source." ) + line = 20, + message = "Can't generate mapping method for a generic type variable source." ) } ) public void shouldFailOnTypeVarTarget() { } - - @Test + @ProcessorTest @WithClasses( { BeanMapper.class, GoodIdea.class, CunningPlan.class } ) + @WithJavaxJaxb public void shouldMapBean() { GoodIdea aGoodIdea = new GoodIdea(); - aGoodIdea.setContent( new JAXBElement( new QName( "test" ), BigDecimal.class, BigDecimal.ONE ) ); + aGoodIdea.setContent( new JAXBElement<>( new QName( "test" ), BigDecimal.class, BigDecimal.ONE ) ); aGoodIdea.setDescription( BigDecimal.ZERO ); CunningPlan aCunningPlan = BeanMapper.STM.transformA( aGoodIdea ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapper.java index 913f5cece0..b635f6b023 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapper.java @@ -1,29 +1,16 @@ -/** - * Copyright 2012-2015 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.complex; import java.util.List; +import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; -import org.mapstruct.InheritInverseConfiguration; import org.mapstruct.ap.test.complex._target.CarDto; import org.mapstruct.ap.test.complex._target.PersonDto; import org.mapstruct.ap.test.complex.other.DateMapper; @@ -37,22 +24,26 @@ public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); @Mappings({ - @Mapping(source = "numberOfSeats", target = "seatCount"), - @Mapping(source = "manufacturingDate", target = "manufacturingYear") + @Mapping(target = "seatCount", source = "numberOfSeats"), + @Mapping(target = "manufacturingYear", source = "manufacturingDate") }) CarDto carToCarDto(Car car); + @InheritInverseConfiguration Car carDtoToCar(CarDto carDto); List carsToCarDtos(List cars); + @InheritInverseConfiguration List carDtosToCars(List carDtos); PersonDto personToPersonDto(Person person); + @InheritInverseConfiguration Person personDtoToPerson(PersonDto personDto); List personsToPersonDtos(List persons); + @InheritInverseConfiguration List personDtosToPersons(List personDtos); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java index 34377208c2..a1881a7613 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/CarMapperTest.java @@ -1,40 +1,27 @@ -/** - * Copyright 2012-2015 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.complex; -import static org.fest.assertions.Assertions.assertThat; - import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; import java.util.GregorianCalendar; import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.test.complex._target.CarDto; import org.mapstruct.ap.test.complex._target.PersonDto; import org.mapstruct.ap.test.complex.other.DateMapper; import org.mapstruct.ap.test.complex.source.Car; import org.mapstruct.ap.test.complex.source.Category; import org.mapstruct.ap.test.complex.source.Person; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; @WithClasses({ Car.class, @@ -45,23 +32,23 @@ Category.class, DateMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) +@DefaultTimeZone("UTC") public class CarMapperTest { - @Test - public void shouldProvideMapperInstance() throws Exception { + @ProcessorTest + public void shouldProvideMapperInstance() { assertThat( CarMapper.INSTANCE ).isNotNull(); } - @Test + @ProcessorTest public void shouldMapAttributeByName() { //given Car car = new Car( "Morris", 2, - new GregorianCalendar( 1980, 0, 1 ).getTime(), + new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime(), new Person( "Bob" ), - new ArrayList() + new ArrayList<>() ); //when @@ -72,15 +59,15 @@ public void shouldMapAttributeByName() { assertThat( carDto.getMake() ).isEqualTo( car.getMake() ); } - @Test + @ProcessorTest public void shouldMapReferenceAttribute() { //given Car car = new Car( "Morris", 2, - new GregorianCalendar( 1980, 0, 1 ).getTime(), + new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime(), new Person( "Bob" ), - new ArrayList() + new ArrayList<>() ); //when @@ -92,10 +79,10 @@ public void shouldMapReferenceAttribute() { assertThat( carDto.getDriver().getName() ).isEqualTo( "Bob" ); } - @Test + @ProcessorTest public void shouldReverseMapReferenceAttribute() { //given - CarDto carDto = new CarDto( "Morris", 2, "1980", new PersonDto( "Bob" ), new ArrayList() ); + CarDto carDto = new CarDto( "Morris", 2, "1980", new PersonDto( "Bob" ), new ArrayList<>() ); //when Car car = CarMapper.INSTANCE.carDtoToCar( carDto ); @@ -106,15 +93,15 @@ public void shouldReverseMapReferenceAttribute() { assertThat( car.getDriver().getName() ).isEqualTo( "Bob" ); } - @Test + @ProcessorTest public void shouldMapAttributeWithCustomMapping() { //given Car car = new Car( "Morris", 2, - new GregorianCalendar( 1980, 0, 1 ).getTime(), + new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime(), new Person( "Bob" ), - new ArrayList() + new ArrayList<>() ); //when @@ -125,10 +112,10 @@ public void shouldMapAttributeWithCustomMapping() { assertThat( carDto.getSeatCount() ).isEqualTo( car.getNumberOfSeats() ); } - @Test + @ProcessorTest public void shouldConsiderCustomMappingForReverseMapping() { //given - CarDto carDto = new CarDto( "Morris", 2, "1980", new PersonDto( "Bob" ), new ArrayList() ); + CarDto carDto = new CarDto( "Morris", 2, "1980", new PersonDto( "Bob" ), new ArrayList<>() ); //when Car car = CarMapper.INSTANCE.carDtoToCar( carDto ); @@ -138,15 +125,15 @@ public void shouldConsiderCustomMappingForReverseMapping() { assertThat( car.getNumberOfSeats() ).isEqualTo( carDto.getSeatCount() ); } - @Test + @ProcessorTest public void shouldApplyConverter() { //given Car car = new Car( "Morris", 2, - new GregorianCalendar( 1980, 0, 1 ).getTime(), + new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime(), new Person( "Bob" ), - new ArrayList() + new ArrayList<>() ); //when @@ -157,39 +144,41 @@ public void shouldApplyConverter() { assertThat( carDto.getManufacturingYear() ).isEqualTo( "1980" ); } - @Test + @ProcessorTest public void shouldApplyConverterForReverseMapping() { //given - CarDto carDto = new CarDto( "Morris", 2, "1980", new PersonDto( "Bob" ), new ArrayList() ); + CarDto carDto = new CarDto( "Morris", 2, "1980", new PersonDto( "Bob" ), new ArrayList<>() ); //when Car car = CarMapper.INSTANCE.carDtoToCar( carDto ); //then assertThat( car ).isNotNull(); - assertThat( car.getManufacturingDate() ).isEqualTo( new GregorianCalendar( 1980, 0, 1 ).getTime() ); + assertThat( car.getManufacturingDate() ).isEqualTo( + new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() + ); } - @Test + @ProcessorTest public void shouldMapIterable() { //given Car car1 = new Car( "Morris", 2, - new GregorianCalendar( 1980, 0, 1 ).getTime(), + new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime(), new Person( "Bob" ), - new ArrayList() + new ArrayList<>() ); Car car2 = new Car( "Railton", 4, - new GregorianCalendar( 1934, 0, 1 ).getTime(), + new GregorianCalendar( 1934, Calendar.JANUARY, 1 ).getTime(), new Person( "Bill" ), - new ArrayList() + new ArrayList<>() ); //when - List dtos = CarMapper.INSTANCE.carsToCarDtos( new ArrayList( Arrays.asList( car1, car2 ) ) ); + List dtos = CarMapper.INSTANCE.carsToCarDtos( Arrays.asList( car1, car2 ) ); //then assertThat( dtos ).isNotNull(); @@ -206,14 +195,14 @@ public void shouldMapIterable() { assertThat( dtos.get( 1 ).getDriver().getName() ).isEqualTo( "Bill" ); } - @Test + @ProcessorTest public void shouldReverseMapIterable() { //given - CarDto car1 = new CarDto( "Morris", 2, "1980", new PersonDto( "Bob" ), new ArrayList() ); - CarDto car2 = new CarDto( "Railton", 4, "1934", new PersonDto( "Bill" ), new ArrayList() ); + CarDto car1 = new CarDto( "Morris", 2, "1980", new PersonDto( "Bob" ), new ArrayList<>() ); + CarDto car2 = new CarDto( "Railton", 4, "1934", new PersonDto( "Bill" ), new ArrayList<>() ); //when - List cars = CarMapper.INSTANCE.carDtosToCars( new ArrayList( Arrays.asList( car1, car2 ) ) ); + List cars = CarMapper.INSTANCE.carDtosToCars( Arrays.asList( car1, car2 ) ); //then assertThat( cars ).isNotNull(); @@ -221,24 +210,28 @@ public void shouldReverseMapIterable() { assertThat( cars.get( 0 ).getMake() ).isEqualTo( "Morris" ); assertThat( cars.get( 0 ).getNumberOfSeats() ).isEqualTo( 2 ); - assertThat( cars.get( 0 ).getManufacturingDate() ).isEqualTo( new GregorianCalendar( 1980, 0, 1 ).getTime() ); + assertThat( cars.get( 0 ).getManufacturingDate() ).isEqualTo( + new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() + ); assertThat( cars.get( 0 ).getDriver().getName() ).isEqualTo( "Bob" ); assertThat( cars.get( 1 ).getMake() ).isEqualTo( "Railton" ); assertThat( cars.get( 1 ).getNumberOfSeats() ).isEqualTo( 4 ); - assertThat( cars.get( 1 ).getManufacturingDate() ).isEqualTo( new GregorianCalendar( 1934, 0, 1 ).getTime() ); + assertThat( cars.get( 1 ).getManufacturingDate() ).isEqualTo( + new GregorianCalendar( 1934, Calendar.JANUARY, 1 ).getTime() + ); assertThat( cars.get( 1 ).getDriver().getName() ).isEqualTo( "Bill" ); } - @Test + @ProcessorTest public void shouldMapIterableAttribute() { //given Car car = new Car( "Morris", 2, - new GregorianCalendar( 1980, 0, 1 ).getTime(), + new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime(), new Person( "Bob" ), - new ArrayList( Arrays.asList( new Person( "Alice" ), new Person( "Bill" ) ) ) + Arrays.asList( new Person( "Alice" ), new Person( "Bill" ) ) ); //when @@ -252,7 +245,7 @@ public void shouldMapIterableAttribute() { assertThat( dto.getPassengers().get( 1 ).getName() ).isEqualTo( "Bill" ); } - @Test + @ProcessorTest public void shouldReverseMapIterableAttribute() { //given CarDto carDto = new CarDto( @@ -260,7 +253,7 @@ public void shouldReverseMapIterableAttribute() { 2, "1980", new PersonDto( "Bob" ), - new ArrayList( Arrays.asList( new PersonDto( "Alice" ), new PersonDto( "Bill" ) ) ) + Arrays.asList( new PersonDto( "Alice" ), new PersonDto( "Bill" ) ) ); //when @@ -274,7 +267,7 @@ public void shouldReverseMapIterableAttribute() { assertThat( car.getPassengers().get( 1 ).getName() ).isEqualTo( "Bill" ); } - @Test + @ProcessorTest public void shouldMapEnumToString() { //given Car car = new Car(); @@ -287,7 +280,7 @@ public void shouldMapEnumToString() { assertThat( carDto.getCategory() ).isEqualTo( "CONVERTIBLE" ); } - @Test + @ProcessorTest public void shouldMapStringToEnum() { //given CarDto carDto = new CarDto(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/_target/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/complex/_target/CarDto.java index 4d6a9bf7fb..8878398be7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/_target/CarDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/_target/CarDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.complex._target; diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/_target/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/complex/_target/PersonDto.java index 2deef41814..cfe527a9c7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/_target/PersonDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/_target/PersonDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.complex._target; diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/other/DateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/complex/other/DateMapper.java index 4f5e32100c..3417d5be5f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/other/DateMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/other/DateMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.complex.other; diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/source/Car.java b/processor/src/test/java/org/mapstruct/ap/test/complex/source/Car.java index 9187080d24..bfdc4d2c87 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/source/Car.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/source/Car.java @@ -1,27 +1,13 @@ -/** - * Copyright 2012-2015 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.complex.source; import java.util.Date; import java.util.List; - public class Car { private String make; diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/source/Category.java b/processor/src/test/java/org/mapstruct/ap/test/complex/source/Category.java index ccf7d96720..ca589cf2dd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/source/Category.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/source/Category.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.complex.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/complex/source/Person.java b/processor/src/test/java/org/mapstruct/ap/test/complex/source/Person.java index 0a75d22617..33dfcc50ca 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/complex/source/Person.java +++ b/processor/src/test/java/org/mapstruct/ap/test/complex/source/Person.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.complex.source; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/Employee.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/Employee.java new file mode 100644 index 0000000000..ab7cdf4adf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/Employee.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.test.conditional; + +public class Employee { + private String name; + private String ssid; + private String nin; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSsid() { + return ssid; + } + + public void setSsid(String ssid) { + this.ssid = ssid; + } + + public String getNin() { + return nin; + } + + public void setNin(String nin) { + this.nin = nin; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/EmployeeDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/EmployeeDto.java new file mode 100644 index 0000000000..f8b9b319fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/EmployeeDto.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.test.conditional; + +public class EmployeeDto { + private String name; + private String country; + private String uniqueIdNumber; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getUniqueIdNumber() { + return uniqueIdNumber; + } + + public void setUniqueIdNumber(String uniqueIdNumber) { + this.uniqueIdNumber = uniqueIdNumber; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/BasicEmployee.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/BasicEmployee.java new file mode 100644 index 0000000000..9fcaa6e761 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/BasicEmployee.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.ap.test.conditional.basic; + +/** + * @author Filip Hrisafov + */ +public class BasicEmployee { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/BasicEmployeeDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/BasicEmployeeDto.java new file mode 100644 index 0000000000..24a944137c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/BasicEmployeeDto.java @@ -0,0 +1,32 @@ +/* + * 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.test.conditional.basic; + +/** + * @author Filip Hrisafov + */ +public class BasicEmployeeDto { + + private final String name; + private final String strategy; + + public BasicEmployeeDto(String name) { + this( name, "default" ); + } + + public BasicEmployeeDto(String name, String strategy) { + this.name = name; + this.strategy = strategy; + } + + public String getName() { + return name; + } + + public String getStrategy() { + return strategy; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java new file mode 100644 index 0000000000..cb2907fd26 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java @@ -0,0 +1,445 @@ +/* + * 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.test.conditional.basic; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2051") +@WithClasses({ + BasicEmployee.class, + BasicEmployeeDto.class +}) +public class ConditionalMappingTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapper.class + }) + public void conditionalMethodInMapper() { + generatedSource.addComparisonToFixtureFor( ConditionalMethodInMapper.class ); + ConditionalMethodInMapper mapper = ConditionalMethodInMapper.INSTANCE; + + BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ) ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + employee = mapper.map( new BasicEmployeeDto( "" ) ); + assertThat( employee.getName() ).isNull(); + + employee = mapper.map( new BasicEmployeeDto( " " ) ); + assertThat( employee.getName() ).isNull(); + } + + @IssueKey( "2882" ) + @ProcessorTest + @WithClasses( { ConditionalMethodWithTargetType.class } ) + public void conditionalMethodWithTargetTypeShouldCompile() { + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodAndBeanPresenceCheckMapper.class + }) + public void conditionalMethodAndBeanPresenceCheckMapper() { + ConditionalMethodAndBeanPresenceCheckMapper mapper = ConditionalMethodAndBeanPresenceCheckMapper.INSTANCE; + + BasicEmployee employee = mapper.map( new ConditionalMethodAndBeanPresenceCheckMapper.EmployeeDto( "Tester" ) ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + employee = mapper.map( new ConditionalMethodAndBeanPresenceCheckMapper.EmployeeDto( "" ) ); + assertThat( employee.getName() ).isNull(); + + employee = mapper.map( new ConditionalMethodAndBeanPresenceCheckMapper.EmployeeDto( " " ) ); + assertThat( employee.getName() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInUsesMapper.class + }) + public void conditionalMethodInUsesMapper() { + ConditionalMethodInUsesMapper mapper = ConditionalMethodInUsesMapper.INSTANCE; + + BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ) ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + employee = mapper.map( new BasicEmployeeDto( "" ) ); + assertThat( employee.getName() ).isNull(); + + employee = mapper.map( new BasicEmployeeDto( " " ) ); + assertThat( employee.getName() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInUsesStaticMapper.class + }) + public void conditionalMethodInUsesStaticMapper() { + ConditionalMethodInUsesStaticMapper mapper = ConditionalMethodInUsesStaticMapper.INSTANCE; + + BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ) ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + employee = mapper.map( new BasicEmployeeDto( "" ) ); + assertThat( employee.getName() ).isNull(); + + employee = mapper.map( new BasicEmployeeDto( " " ) ); + assertThat( employee.getName() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInContextMapper.class + }) + public void conditionalMethodInUsesContextMapper() { + ConditionalMethodInContextMapper mapper = ConditionalMethodInContextMapper.INSTANCE; + + ConditionalMethodInContextMapper.PresenceUtils utils = new ConditionalMethodInContextMapper.PresenceUtils(); + BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ), utils ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + employee = mapper.map( new BasicEmployeeDto( "" ), utils ); + assertThat( employee.getName() ).isNull(); + + employee = mapper.map( new BasicEmployeeDto( " " ), utils ); + assertThat( employee.getName() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourceParameterMapper.class + }) + public void conditionalMethodWithSourceParameter() { + ConditionalMethodWithSourceParameterMapper mapper = ConditionalMethodWithSourceParameterMapper.INSTANCE; + + BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ) ); + assertThat( employee.getName() ).isNull(); + + employee = mapper.map( new BasicEmployeeDto( "Tester", "map" ) ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourceParameterAndValueMapper.class + }) + public void conditionalMethodWithSourceParameterAndValue() { + generatedSource.addComparisonToFixtureFor( ConditionalMethodWithSourceParameterAndValueMapper.class ); + ConditionalMethodWithSourceParameterAndValueMapper mapper = + ConditionalMethodWithSourceParameterAndValueMapper.INSTANCE; + + BasicEmployee employee = mapper.map( new BasicEmployeeDto( " ", "empty" ) ); + assertThat( employee.getName() ).isEqualTo( " " ); + + employee = mapper.map( new BasicEmployeeDto( " ", "blank" ) ); + assertThat( employee.getName() ).isNull(); + + employee = mapper.map( new BasicEmployeeDto( "Tester", "blank" ) ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + } + + @ProcessorTest + @WithClasses({ + ErroneousAmbiguousConditionalMethodMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousAmbiguousConditionalMethodMapper.class, + line = 17, + message = "Ambiguous presence check methods found for checking String: " + + "boolean isNotBlank(String value), " + + "boolean isNotEmpty(String value). " + + "See https://mapstruct.org/faq/#ambiguous for more info." + ) + } + ) + public void ambiguousConditionalMethod() { + + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForCollectionMapper.class + }) + public void conditionalMethodForCollection() { + ConditionalMethodForCollectionMapper mapper = ConditionalMethodForCollectionMapper.INSTANCE; + + ConditionalMethodForCollectionMapper.Author author = new ConditionalMethodForCollectionMapper.Author(); + ConditionalMethodForCollectionMapper.AuthorDto dto = mapper.map( author ); + + assertThat( dto.getBooks() ).isNull(); + + author.setBooks( Collections.emptyList() ); + dto = mapper.map( author ); + + assertThat( dto.getBooks() ).isNull(); + + author.setBooks( Arrays.asList( + new ConditionalMethodForCollectionMapper.Book( "Test" ), + new ConditionalMethodForCollectionMapper.Book( "Test Vol. 2" ) + ) ); + dto = mapper.map( author ); + + assertThat( dto.getBooks() ) + .extracting( ConditionalMethodForCollectionMapper.BookDto::getName ) + .containsExactly( "Test", "Test Vol. 2" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForSourceBeanMapper.class + }) + public void conditionalMethodForSourceBean() { + ConditionalMethodForSourceBeanMapper mapper = ConditionalMethodForSourceBeanMapper.INSTANCE; + + ConditionalMethodForSourceBeanMapper.Employee employee = mapper.map( + new ConditionalMethodForSourceBeanMapper.EmployeeDto( + "1", + "Tester" + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + + employee = mapper.map( null ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceBeanMapper.EmployeeDto( null, "Tester" ) ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceBeanMapper.EmployeeDto( "test-123", "Tester" ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "test-123" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForSourceParameterAndPropertyMapper.class + }) + public void conditionalMethodForSourceParameterAndProperty() { + ConditionalMethodForSourceParameterAndPropertyMapper mapper = + ConditionalMethodForSourceParameterAndPropertyMapper.INSTANCE; + + ConditionalMethodForSourceParameterAndPropertyMapper.Employee employee = mapper.map( + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester" + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + assertThat( employee.getManager() ).isNull(); + + employee = mapper.map( null ); + + assertThat( employee ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester", + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( null, "Manager" ) + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getManager() ).isNull(); + + employee = mapper.map( new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( + "1", + "Tester", + new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( "2", "Manager" ) + ) ); + + assertThat( employee ).isNotNull(); + assertThat( employee.getId() ).isEqualTo( "1" ); + assertThat( employee.getName() ).isEqualTo( "Tester" ); + assertThat( employee.getManager() ).isNotNull(); + assertThat( employee.getManager().getId() ).isEqualTo( "2" ); + assertThat( employee.getManager().getName() ).isEqualTo( "Manager" ); + } + + @ProcessorTest + @WithClasses({ + OptionalLikeConditionalMapper.class + }) + @IssueKey("2084") + public void optionalLikeConditional() { + OptionalLikeConditionalMapper mapper = OptionalLikeConditionalMapper.INSTANCE; + + OptionalLikeConditionalMapper.Target target = mapper.map( new OptionalLikeConditionalMapper.Source( + OptionalLikeConditionalMapper.Nullable.ofNullable( "test" ) ) ); + + assertThat( target.getValue() ).isEqualTo( "test" ); + + target = mapper.map( + new OptionalLikeConditionalMapper.Source( OptionalLikeConditionalMapper.Nullable.undefined() ) + ); + + assertThat( target.getValue() ).isEqualTo( "initial" ); + + } + + @ProcessorTest + @WithClasses( { + ConditionalMethodWithMappingTargetInUpdateMapper.class + } ) + @IssueKey( "2758" ) + public void conditionalMethodWithMappingTarget() { + ConditionalMethodWithMappingTargetInUpdateMapper mapper = + ConditionalMethodWithMappingTargetInUpdateMapper.INSTANCE; + + BasicEmployee targetEmployee = new BasicEmployee(); + targetEmployee.setName( "CurrentName" ); + mapper.map( new BasicEmployeeDto( "ReplacementName" ), targetEmployee ); + + assertThat( targetEmployee.getName() ).isEqualTo( "CurrentName" ); + } + + @ProcessorTest + @WithClasses({ + ErroneousConditionalWithoutAppliesToMethodMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousConditionalWithoutAppliesToMethodMapper.class, + line = 19, + message = "'appliesTo' has to have at least one value in @Condition" + ) + } + ) + public void emptyConditional() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithMappingTargetMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithMappingTargetMapper.class, + line = 21, + message = "Parameter \"@MappingTarget BasicEmployee employee\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithMappingTarget() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithTargetTypeMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithTargetTypeMapper.class, + line = 21, + message = "Parameter \"@TargetType Class targetClass\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithTargetType() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.class, + line = 21, + message = "Parameter \"@TargetPropertyName String targetProperty\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParameterConditionalWithTargetPropertyName() { + } + + @ProcessorTest + @WithClasses({ + ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.class, + line = 21, + message = "Parameter \"@SourcePropertyName String sourceProperty\"" + + " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." + + " Only source and @Context parameters are allowed for conditions applicable to source parameters." + ) + } + ) + public void sourceParametersConditionalWithSourcePropertyName() { + } + + @ProcessorTest + @WithClasses({ + ErroneousAmbiguousSourceParameterConditionalMethodMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousAmbiguousSourceParameterConditionalMethodMapper.class, + line = 17, + message = "Ambiguous source parameter check methods found for checking BasicEmployeeDto: " + + "boolean hasName(BasicEmployeeDto value), " + + "boolean hasStrategy(BasicEmployeeDto value). " + + "See https://mapstruct.org/faq/#ambiguous for more info." + ) + } + ) + public void ambiguousSourceParameterConditionalMethod() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodAndBeanPresenceCheckMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodAndBeanPresenceCheckMapper.java new file mode 100644 index 0000000000..ad37d5832b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodAndBeanPresenceCheckMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodAndBeanPresenceCheckMapper { + + ConditionalMethodAndBeanPresenceCheckMapper INSTANCE = Mappers.getMapper( + ConditionalMethodAndBeanPresenceCheckMapper.class ); + + BasicEmployee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + + class EmployeeDto { + + private final String name; + + public EmployeeDto(String name) { + this.name = name; + } + + public boolean hasName() { + return false; + } + + public String getName() { + return name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForCollectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForCollectionMapper.java new file mode 100644 index 0000000000..a27a0aaf0a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForCollectionMapper.java @@ -0,0 +1,81 @@ +/* + * 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.test.conditional.basic; + +import java.util.Collection; +import java.util.List; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodForCollectionMapper { + + ConditionalMethodForCollectionMapper INSTANCE = Mappers.getMapper( ConditionalMethodForCollectionMapper.class ); + + AuthorDto map(Author author); + + @Condition + default boolean isNotEmpty(Collection collection) { + return collection != null && !collection.isEmpty(); + } + + class Author { + private List books; + + public List getBooks() { + return books; + } + + public boolean hasBooks() { + return false; + } + + public void setBooks(List books) { + this.books = books; + } + } + + class Book { + private final String name; + + public Book(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + class AuthorDto { + private List books; + + public List getBooks() { + return books; + } + + public void setBooks(List books) { + this.books = books; + } + } + + class BookDto { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java new file mode 100644 index 0000000000..dd2fe4ac90 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java @@ -0,0 +1,64 @@ +/* + * 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.test.conditional.basic; + +import org.mapstruct.Mapper; +import org.mapstruct.SourceParameterCondition; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodForSourceBeanMapper { + + ConditionalMethodForSourceBeanMapper INSTANCE = Mappers.getMapper( ConditionalMethodForSourceBeanMapper.class ); + + Employee map(EmployeeDto employee); + + @SourceParameterCondition + default boolean canMapEmployeeDto(EmployeeDto employee) { + return employee != null && employee.getId() != null; + } + + class EmployeeDto { + + private final String id; + private final String name; + + public EmployeeDto(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } + + class Employee { + + private final String id; + private final String name; + + public Employee(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java new file mode 100644 index 0000000000..11a97b7239 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java @@ -0,0 +1,85 @@ +/* + * 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.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodForSourceParameterAndPropertyMapper { + + ConditionalMethodForSourceParameterAndPropertyMapper INSTANCE = Mappers.getMapper( + ConditionalMethodForSourceParameterAndPropertyMapper.class ); + + Employee map(EmployeeDto employee); + + @Condition(appliesTo = { + ConditionStrategy.SOURCE_PARAMETERS, + ConditionStrategy.PROPERTIES + }) + default boolean canMapEmployeeDto(EmployeeDto employee) { + return employee != null && employee.getId() != null; + } + + class EmployeeDto { + + private final String id; + private final String name; + private final EmployeeDto manager; + + public EmployeeDto(String id, String name) { + this( id, name, null ); + } + + public EmployeeDto(String id, String name, EmployeeDto manager) { + this.id = id; + this.name = name; + this.manager = manager; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public EmployeeDto getManager() { + return manager; + } + } + + class Employee { + + private final String id; + private final String name; + private final Employee manager; + + public Employee(String id, String name, Employee manager) { + this.id = id; + this.name = name; + this.manager = manager; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public Employee getManager() { + return manager; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInContextMapper.java new file mode 100644 index 0000000000..26ad844146 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInContextMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodInContextMapper { + + ConditionalMethodInContextMapper INSTANCE = Mappers.getMapper( ConditionalMethodInContextMapper.class ); + + BasicEmployee map(BasicEmployeeDto employee, @Context PresenceUtils utils); + + class PresenceUtils { + @Condition + public boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInMapper.java new file mode 100644 index 0000000000..256419f687 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodInMapper { + + ConditionalMethodInMapper INSTANCE = Mappers.getMapper( ConditionalMethodInMapper.class ); + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition + default boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInUsesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInUsesMapper.java new file mode 100644 index 0000000000..fb30d19c80 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInUsesMapper.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.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = ConditionalMethodInUsesMapper.PresenceUtils.class) +public interface ConditionalMethodInUsesMapper { + + ConditionalMethodInUsesMapper INSTANCE = Mappers.getMapper( ConditionalMethodInUsesMapper.class ); + + BasicEmployee map(BasicEmployeeDto employee); + + class PresenceUtils { + + @Condition + public boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInUsesStaticMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInUsesStaticMapper.java new file mode 100644 index 0000000000..5e77d6f8fb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodInUsesStaticMapper.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.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = ConditionalMethodInUsesStaticMapper.PresenceUtils.class) +public interface ConditionalMethodInUsesStaticMapper { + + ConditionalMethodInUsesStaticMapper INSTANCE = Mappers.getMapper( ConditionalMethodInUsesStaticMapper.class ); + + BasicEmployee map(BasicEmployeeDto employee); + + interface PresenceUtils { + + @Condition + static boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithMappingTargetInUpdateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithMappingTargetInUpdateMapper.java new file mode 100644 index 0000000000..12f4a00212 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithMappingTargetInUpdateMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper( nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE ) +public interface ConditionalMethodWithMappingTargetInUpdateMapper { + + ConditionalMethodWithMappingTargetInUpdateMapper INSTANCE = + Mappers.getMapper( ConditionalMethodWithMappingTargetInUpdateMapper.class ); + + void map(BasicEmployeeDto employee, @MappingTarget BasicEmployee targetEmployee); + + @Condition + default boolean isNotBlankAndNotPresent(String value, @MappingTarget BasicEmployee targetEmployee) { + return value != null && !value.trim().isEmpty() && targetEmployee.getName() == null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithSourceParameterAndValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithSourceParameterAndValueMapper.java new file mode 100644 index 0000000000..e39c5c88e8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithSourceParameterAndValueMapper.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.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodWithSourceParameterAndValueMapper { + + ConditionalMethodWithSourceParameterAndValueMapper INSTANCE = Mappers.getMapper( + ConditionalMethodWithSourceParameterAndValueMapper.class ); + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition + default boolean isPresent(BasicEmployeeDto source, String value) { + if ( value == null ) { + return false; + } + switch ( source.getStrategy() ) { + case "blank": + return !value.trim().isEmpty(); + case "empty": + return !value.isEmpty(); + default: + return true; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithSourceParameterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithSourceParameterMapper.java new file mode 100644 index 0000000000..9a499a6039 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithSourceParameterMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodWithSourceParameterMapper { + + ConditionalMethodWithSourceParameterMapper INSTANCE = + Mappers.getMapper( ConditionalMethodWithSourceParameterMapper.class ); + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition + default boolean shouldMap(BasicEmployeeDto source) { + return "map".equals( source.getStrategy() ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithTargetType.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithTargetType.java new file mode 100644 index 0000000000..5ec242d8ff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodWithTargetType.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +/** + * @author Ben Zegveld + */ +@Mapper +public interface ConditionalMethodWithTargetType { + + ConditionalMethodWithTargetType INSTANCE = Mappers.getMapper( ConditionalMethodWithTargetType.class ); + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @TargetType Class targetType) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousConditionalMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousConditionalMethodMapper.java new file mode 100644 index 0000000000..49e354c913 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousConditionalMethodMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousAmbiguousConditionalMethodMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition + default boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + + @Condition + default boolean isNotEmpty(String value) { + return value != null && value.isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java new file mode 100644 index 0000000000..39433e2641 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Mapper; +import org.mapstruct.SourceParameterCondition; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousAmbiguousSourceParameterConditionalMethodMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @SourceParameterCondition + default boolean hasName(BasicEmployeeDto value) { + return value != null && value.getName() != null; + } + + @SourceParameterCondition + default boolean hasStrategy(BasicEmployeeDto value) { + return value != null && value.getStrategy() != null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java new file mode 100644 index 0000000000..76f62845d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousConditionalWithoutAppliesToMethodMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = {}) + default boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java new file mode 100644 index 0000000000..b1206798d6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithMappingTargetMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @MappingTarget BasicEmployee employee) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java new file mode 100644 index 0000000000..1bfd150afc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.SourcePropertyName; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithSourcePropertyNameMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @SourcePropertyName String sourceProperty) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java new file mode 100644 index 0000000000..e9dac1ece5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.TargetPropertyName; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithTargetPropertyNameMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @TargetPropertyName String targetProperty) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java new file mode 100644 index 0000000000..9ff55597ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.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.ap.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousSourceParameterConditionalWithTargetTypeMapper { + + BasicEmployee map(BasicEmployeeDto employee); + + @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS) + default boolean isNotBlank(String value, @TargetType Class targetClass) { + return value != null && !value.trim().isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/OptionalLikeConditionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/OptionalLikeConditionalMapper.java new file mode 100644 index 0000000000..4d097d7b5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/OptionalLikeConditionalMapper.java @@ -0,0 +1,77 @@ +/* + * 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.test.conditional.basic; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalLikeConditionalMapper { + + OptionalLikeConditionalMapper INSTANCE = Mappers.getMapper( OptionalLikeConditionalMapper.class ); + + Target map(Source source); + + default T map(Nullable nullable) { + return nullable.value; + } + + @Condition + default boolean isPresent(Nullable nullable) { + return nullable.isPresent(); + } + + class Nullable { + + private final T value; + private final boolean present; + + private Nullable(T value, boolean present) { + this.value = value; + this.present = present; + } + + public boolean isPresent() { + return present; + } + + public static Nullable undefined() { + return new Nullable<>( null, false ); + } + + public static Nullable ofNullable(T value) { + return new Nullable<>( value, true ); + } + } + + class Source { + protected final Nullable value; + + public Source(Nullable value) { + this.value = value; + } + + public Nullable getValue() { + return value; + } + } + + class Target { + protected String value = "initial"; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java new file mode 100644 index 0000000000..1ad36f7de1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalExpressionTest.java @@ -0,0 +1,144 @@ +/* + * 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.test.conditional.expression; + +import org.mapstruct.ap.test.conditional.Employee; +import org.mapstruct.ap.test.conditional.EmployeeDto; +import org.mapstruct.ap.test.conditional.basic.BasicEmployee; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2051") +@WithClasses({ + Employee.class, + EmployeeDto.class +}) +public class ConditionalExpressionTest { + + @ProcessorTest + @WithClasses({ + ConditionalMethodsInUtilClassMapper.class + }) + public void conditionalExpressionInStaticClassMethod() { + ConditionalMethodsInUtilClassMapper mapper = ConditionalMethodsInUtilClassMapper.INSTANCE; + + EmployeeDto dto = new EmployeeDto(); + dto.setName( "Tester" ); + dto.setUniqueIdNumber( "SSID-001" ); + dto.setCountry( null ); + + Employee employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isNull(); + + dto.setCountry( "UK" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isEqualTo( "SSID-001" ); + assertThat( employee.getSsid() ).isNull(); + + dto.setCountry( "US" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isEqualTo( "SSID-001" ); + + dto.setCountry( "CH" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + SimpleConditionalExpressionMapper.class + }) + public void conditionalSimpleExpression() { + SimpleConditionalExpressionMapper mapper = SimpleConditionalExpressionMapper.INSTANCE; + + SimpleConditionalExpressionMapper.Target target = + mapper.map( new SimpleConditionalExpressionMapper.Source( 50 ) ); + assertThat( target.getValue() ).isEqualTo( 50 ); + + target = mapper.map( new SimpleConditionalExpressionMapper.Source( 101 ) ); + assertThat( target.getValue() ).isEqualTo( 0 ); + } + + @ProcessorTest + @WithClasses({ + ConditionalWithSourceToTargetExpressionMapper.class + }) + @IssueKey("2666") + public void conditionalExpressionForSourceToTarget() { + ConditionalWithSourceToTargetExpressionMapper mapper = ConditionalWithSourceToTargetExpressionMapper.INSTANCE; + + ConditionalWithSourceToTargetExpressionMapper.OrderDTO orderDto = + new ConditionalWithSourceToTargetExpressionMapper.OrderDTO(); + + ConditionalWithSourceToTargetExpressionMapper.Order order = mapper.convertToOrder( orderDto ); + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNull(); + + orderDto = new ConditionalWithSourceToTargetExpressionMapper.OrderDTO(); + orderDto.setCustomerName( "Tester" ); + order = mapper.convertToOrder( orderDto ); + + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNotNull(); + assertThat( order.getCustomer().getName() ).isEqualTo( "Tester" ); + assertThat( order.getCustomer().getAddress() ).isNull(); + + orderDto = new ConditionalWithSourceToTargetExpressionMapper.OrderDTO(); + orderDto.setLine1( "Line 1" ); + order = mapper.convertToOrder( orderDto ); + + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNotNull(); + assertThat( order.getCustomer().getName() ).isNull(); + assertThat( order.getCustomer().getAddress() ).isNotNull(); + assertThat( order.getCustomer().getAddress().getLine1() ).isEqualTo( "Line 1" ); + assertThat( order.getCustomer().getAddress().getLine2() ).isNull(); + + } + + @IssueKey("2794") + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousConditionExpressionMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Value for condition expression must be given in the form \"java()\"." + ), + @Diagnostic(type = ErroneousConditionExpressionMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 22, + message = "Constant and condition expression are both defined in @Mapping," + + " either define a constant or a condition expression." + ), + @Diagnostic(type = ErroneousConditionExpressionMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 25, + message = "Expression and condition expression are both defined in @Mapping," + + " either define an expression or a condition expression." + ) + } + ) + @WithClasses({ + BasicEmployee.class, + ErroneousConditionExpressionMapper.class + } ) + public void invalidJavaConditionExpression() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalMethodsInUtilClassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalMethodsInUtilClassMapper.java new file mode 100644 index 0000000000..3c49de5bf0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalMethodsInUtilClassMapper.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.test.conditional.expression; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.conditional.Employee; +import org.mapstruct.ap.test.conditional.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(imports = ConditionalMethodsInUtilClassMapper.StaticUtil.class) +public interface ConditionalMethodsInUtilClassMapper { + + ConditionalMethodsInUtilClassMapper INSTANCE = Mappers.getMapper( ConditionalMethodsInUtilClassMapper.class ); + + @Mapping(target = "ssid", source = "uniqueIdNumber", + conditionExpression = "java(StaticUtil.isAmericanCitizen( employee ))") + @Mapping(target = "nin", source = "uniqueIdNumber", + conditionExpression = "java(StaticUtil.isBritishCitizen( employee ))") + Employee map(EmployeeDto employee); + + interface StaticUtil { + + static boolean isAmericanCitizen(EmployeeDto employeeDto) { + return "US".equals( employeeDto.getCountry() ); + } + + static boolean isBritishCitizen(EmployeeDto employeeDto) { + return "UK".equals( employeeDto.getCountry() ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalWithSourceToTargetExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalWithSourceToTargetExpressionMapper.java new file mode 100644 index 0000000000..c32fe99e03 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ConditionalWithSourceToTargetExpressionMapper.java @@ -0,0 +1,132 @@ +/* + * 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.test.conditional.expression; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(imports = ConditionalWithSourceToTargetExpressionMapper.Util.class) +public interface ConditionalWithSourceToTargetExpressionMapper { + + ConditionalWithSourceToTargetExpressionMapper INSTANCE = + Mappers.getMapper( ConditionalWithSourceToTargetExpressionMapper.class ); + + @Mapping(source = "orderDTO", target = "customer", + conditionExpression = "java(Util.mapCustomerFromOrder( orderDTO ))") + Order convertToOrder(OrderDTO orderDTO); + + @Mapping(source = "customerName", target = "name") + @Mapping(source = "orderDTO", target = "address", + conditionExpression = "java(Util.mapAddressFromOrder( orderDTO ))") + Customer convertToCustomer(OrderDTO orderDTO); + + Address convertToAddress(OrderDTO orderDTO); + + class OrderDTO { + + private String customerName; + private String line1; + private String line2; + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + + } + + class Order { + + private Customer customer; + + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + } + + class Customer { + private String name; + private Address address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + class Address { + + private String line1; + private String line2; + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + } + + interface Util { + + static boolean mapCustomerFromOrder(OrderDTO orderDTO) { + return orderDTO != null && ( orderDTO.getCustomerName() != null || mapAddressFromOrder( orderDTO ) ); + } + + static boolean mapAddressFromOrder(OrderDTO orderDTO) { + return orderDTO != null && ( orderDTO.getLine1() != null || orderDTO.getLine2() != null ); + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ErroneousConditionExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ErroneousConditionExpressionMapper.java new file mode 100644 index 0000000000..c8e5db243e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/ErroneousConditionExpressionMapper.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.ap.test.conditional.expression; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.conditional.EmployeeDto; +import org.mapstruct.ap.test.conditional.basic.BasicEmployee; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousConditionExpressionMapper { + + @Mapping(target = "name", conditionExpression = "!employee.getName().isEmpty()") + BasicEmployee map(EmployeeDto employee); + + @Mapping(target = "name", conditionExpression = "java(true)", constant = "test") + BasicEmployee mapConstant(EmployeeDto employee); + + @Mapping(target = "name", conditionExpression = "java(true)", expression = "java(\"test\")") + BasicEmployee mapExpression(EmployeeDto employee); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/SimpleConditionalExpressionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/SimpleConditionalExpressionMapper.java new file mode 100644 index 0000000000..861fa0c1e3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/expression/SimpleConditionalExpressionMapper.java @@ -0,0 +1,46 @@ +/* + * 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.test.conditional.expression; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleConditionalExpressionMapper { + + SimpleConditionalExpressionMapper INSTANCE = Mappers.getMapper( SimpleConditionalExpressionMapper.class ); + + @Mapping(target = "value", conditionExpression = "java(source.getValue() < 100)") + Target map(Source source); + + class Source { + private final int value; + + public Source(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + class Target { + private int value; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java new file mode 100644 index 0000000000..339af75944 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.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.ap.test.conditional.propertyname; + +/** + * @author Nikola Ivačič + */ +public class Address implements DomainModel { + private String street; + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java new file mode 100644 index 0000000000..c6a63065d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.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.ap.test.conditional.propertyname; + +/** + * @author Nikola Ivačič + */ +public class AddressDto implements DomainModel { + private final String street; + + public AddressDto(String street) { + this.street = street; + } + + public String getStreet() { + return street; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java new file mode 100644 index 0000000000..8e5fa8695d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.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.ap.test.conditional.propertyname; + +/** + * Target Property Name test entities + * + * @author Nikola Ivačič + */ +public interface DomainModel { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java new file mode 100644 index 0000000000..497717a592 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.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.ap.test.conditional.propertyname; + +import java.util.List; + +/** + * @author Nikola Ivačič + */ +public class Employee implements DomainModel { + + private String firstName; + private String lastName; + private String title; + private String country; + private boolean active; + private int age; + + private Employee boss; + + private Address primaryAddress; + + private List
      addresses; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public Employee getBoss() { + return boss; + } + + public void setBoss(Employee boss) { + this.boss = boss; + } + + public Address getPrimaryAddress() { + return primaryAddress; + } + + public void setPrimaryAddress(Address primaryAddress) { + this.primaryAddress = primaryAddress; + } + + public List
      getAddresses() { + return addresses; + } + + public void setAddresses(List
      addresses) { + this.addresses = addresses; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java new file mode 100644 index 0000000000..f49f25e4a3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.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.ap.test.conditional.propertyname; + +import java.util.List; + +/** + * @author Nikola Ivačič + */ +public class EmployeeDto implements DomainModel { + + private String firstName; + private String lastName; + private String title; + private String originCountry; + private boolean active; + private int age; + + private EmployeeDto boss; + + private AddressDto primaryAddress; + private List originAddresses; + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getOriginCountry() { + return originCountry; + } + + public void setOriginCountry(String originCountry) { + this.originCountry = originCountry; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public EmployeeDto getBoss() { + return boss; + } + + public void setBoss(EmployeeDto boss) { + this.boss = boss; + } + + public AddressDto getPrimaryAddress() { + return primaryAddress; + } + + public void setPrimaryAddress(AddressDto primaryAddress) { + this.primaryAddress = primaryAddress; + } + + public List getOriginAddresses() { + return originAddresses; + } + + public void setOriginAddresses(List originAddresses) { + this.originAddresses = originAddresses; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..944fe5d146 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java @@ -0,0 +1,48 @@ +/* + * 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.test.conditional.propertyname.sourcepropertyname; + +import java.util.Collection; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodForCollectionMapperWithSourcePropertyName { + + ConditionalMethodForCollectionMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodForCollectionMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotEmpty(Collection collection, @SourcePropertyName String propName) { + if ( "originAddresses".equalsIgnoreCase( propName ) ) { + return false; + } + return collection != null && !collection.isEmpty(); + } + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java new file mode 100644 index 0000000000..ea6a77d94e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.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.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithAllExceptTarget { + + ConditionalMethodInMapperWithAllExceptTarget INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllExceptTarget.class ); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @SourcePropertyName String propName, + @Context PresenceUtils utils) { + utils.visited.add( propName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + if ( propName.equalsIgnoreCase( "firstName" ) ) { + return true; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java new file mode 100644 index 0000000000..ee3be57648 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java @@ -0,0 +1,64 @@ +/* + * 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.test.conditional.propertyname.sourcepropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithAllOptions { + + ConditionalMethodInMapperWithAllOptions INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); + + class PresenceUtils { + Set visitedSourceNames = new LinkedHashSet<>(); + Set visitedTargetNames = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + Set visitedTargets = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + void map(EmployeeDto employeeDto, + @MappingTarget Employee employee, + @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @MappingTarget DomainModel target, + @SourcePropertyName String sourcePropName, + @TargetPropertyName String targetPropName, + @Context PresenceUtils utils) { + utils.visitedSourceNames.add( sourcePropName ); + utils.visitedTargetNames.add( targetPropName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + utils.visitedTargets.add( target.getClass().getSimpleName() ); + if ( sourcePropName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..41ff6c5264 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.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.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithSourcePropertyName { + + ConditionalMethodInMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java new file mode 100644 index 0000000000..8ba222512f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.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.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper(uses = ConditionalMethodInUsesMapperWithSourcePropertyName.PresenceUtils.class) +public interface ConditionalMethodInUsesMapperWithSourcePropertyName { + + ConditionalMethodInUsesMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInUsesMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + class PresenceUtils { + + @Condition + public boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java new file mode 100644 index 0000000000..f69695baa0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java @@ -0,0 +1,108 @@ +/* + * 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.test.conditional.propertyname.sourcepropertyname; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodWithSourcePropertyNameInContextMapper { + + ConditionalMethodWithSourcePropertyNameInContextMapper INSTANCE + = Mappers.getMapper( ConditionalMethodWithSourcePropertyNameInContextMapper.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + Address map(AddressDto addressDto, @Context PresenceUtils utils); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean isNotBlank(String value, @SourcePropertyName String propName) { + visited.add( propName ); + return value != null && !value.trim().isEmpty(); + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); + + class PresenceUtilsAllProps { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean collect(@SourcePropertyName String propName) { + visited.add( propName ); + return true; + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); + + @BeforeMapping + default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource utils) { + String lastProp = utils.visitedSegments.peekLast(); + if ( lastProp != null && source != null ) { + utils.path.offerLast( lastProp ); + } + } + + @AfterMapping + default void after(@TargetType Class targetClass, @Context PresenceUtilsAllPropsWithSource utils) { + // intermediate method for collection mapping must not change the path + if ( targetClass != List.class ) { + utils.path.pollLast(); + } + } + + class PresenceUtilsAllPropsWithSource { + Deque visitedSegments = new LinkedList<>(); + Deque visited = new LinkedList<>(); + Deque path = new LinkedList<>(); + + @Condition + public boolean collect(@SourcePropertyName String propName) { + visitedSegments.offerLast( propName ); + path.offerLast( propName ); + visited.offerLast( String.join( ".", path ) ); + path.pollLast(); + return true; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java new file mode 100644 index 0000000000..5ed118676d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.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.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; + +@Mapper +public interface ErroneousNonStringSourcePropertyNameParameter { + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName int propName) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java new file mode 100644 index 0000000000..2471ed647d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java @@ -0,0 +1,312 @@ +/* + * 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.test.conditional.propertyname.sourcepropertyname; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@IssueKey("3323") +@WithClasses({ + Address.class, + AddressDto.class, + Employee.class, + EmployeeDto.class, + DomainModel.class +}) +public class SourcePropertyNameTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithSourcePropertyName.class + }) + public void conditionalMethodInMapperWithSourcePropertyName() { + ConditionalMethodInMapperWithSourcePropertyName mapper + = ConditionalMethodInMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForCollectionMapperWithSourcePropertyName.class + }) + public void conditionalMethodForCollectionMapperWithSourcePropertyName() { + ConditionalMethodForCollectionMapperWithSourcePropertyName mapper + = ConditionalMethodForCollectionMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInUsesMapperWithSourcePropertyName.class + }) + public void conditionalMethodInUsesMapperWithSourcePropertyName() { + ConditionalMethodInUsesMapperWithSourcePropertyName mapper + = ConditionalMethodInUsesMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllOptions.class + }) + public void conditionalMethodInMapperWithAllOptions() { + ConditionalMethodInMapperWithAllOptions mapper + = ConditionalMethodInMapperWithAllOptions.INSTANCE; + + ConditionalMethodInMapperWithAllOptions.PresenceUtils utils = + new ConditionalMethodInMapperWithAllOptions.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = new Employee(); + mapper.map( employeeDto, employee, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visitedSourceNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" ); + assertThat( utils.visitedTargetNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); + assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); + assertThat( utils.visitedTargets ).containsExactly( "Employee" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllExceptTarget.class + }) + public void conditionalMethodInMapperWithAllExceptTarget() { + ConditionalMethodInMapperWithAllExceptTarget mapper + = ConditionalMethodInMapperWithAllExceptTarget.INSTANCE; + + ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils utils = + new ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isEqualTo( " " ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" ); + assertThat( utils.visitedSources ).containsExactlyInAnyOrder( "EmployeeDto", "AddressDto" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourcePropertyNameInContextMapper.class + }) + public void conditionalMethodWithSourcePropertyNameInUsesContextMapper() { + ConditionalMethodWithSourcePropertyNameInContextMapper mapper + = ConditionalMethodWithSourcePropertyNameInContextMapper.INSTANCE; + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils utils = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setLastName( " " ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" ); + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps allPropsUtils = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps(); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtils ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( allPropsUtils.visited ) + .containsExactlyInAnyOrder( + "firstName", + "lastName", + "title", + "originCountry", + "active", + "age", + "boss", + "primaryAddress", + "originAddresses", + "street" + ); + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource allPropsUtilsWithSource = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource(); + + EmployeeDto bossEmployeeDto = new EmployeeDto(); + bossEmployeeDto.setLastName( "Boss Tester" ); + bossEmployeeDto.setOriginCountry( "US" ); + bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( + "Testing St. 10" ) ) ); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setBoss( bossEmployeeDto ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtilsWithSource ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ).isNotEmpty(); + assertThat( employee.getAddresses().get( 0 ).getStreet() ).isEqualTo( "Testing St. 6" ); + assertThat( employee.getBoss() ).isNotNull(); + assertThat( employee.getBoss().getCountry() ).isEqualTo( "US" ); + assertThat( employee.getBoss().getLastName() ).isEqualTo( "Boss Tester" ); + assertThat( employee.getBoss().getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 10" ); + assertThat( allPropsUtilsWithSource.visited ) + .containsExactly( + "originCountry", + "originAddresses", + "originAddresses.street", + "firstName", + "lastName", + "title", + "active", + "age", + "boss", + "boss.originCountry", + "boss.originAddresses", + "boss.originAddresses.street", + "boss.firstName", + "boss.lastName", + "boss.title", + "boss.active", + "boss.age", + "boss.boss", + "boss.primaryAddress", + "primaryAddress" + ); + } + + @ProcessorTest + @WithClasses({ + ErroneousNonStringSourcePropertyNameParameter.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousNonStringSourcePropertyNameParameter.class, + line = 23, + message = "@SourcePropertyName can only by applied to a String parameter." + ) + } + ) + public void nonStringSourcePropertyNameParameter() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java new file mode 100644 index 0000000000..a51a318eb6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java @@ -0,0 +1,46 @@ +/* + * 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.test.conditional.propertyname.targetpropertyname; + +import java.util.Collection; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodForCollectionMapperWithTargetPropertyName { + + ConditionalMethodForCollectionMapperWithTargetPropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodForCollectionMapperWithTargetPropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotEmpty(Collection collection, @TargetPropertyName String propName) { + if ( "addresses".equalsIgnoreCase( propName ) ) { + return false; + } + return collection != null && !collection.isEmpty(); + } + + @Condition + default boolean isNotBlank(String value, @TargetPropertyName String propName) { + if ( propName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java new file mode 100644 index 0000000000..1d0dd3fa51 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.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.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodInMapperWithAllExceptTarget { + + ConditionalMethodInMapperWithAllExceptTarget INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllExceptTarget.class ); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @TargetPropertyName String propName, + @Context PresenceUtils utils) { + utils.visited.add( propName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + if ( propName.equalsIgnoreCase( "firstName" ) ) { + return true; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java new file mode 100644 index 0000000000..d3ccb9ba2e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.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.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodInMapperWithAllOptions { + + ConditionalMethodInMapperWithAllOptions INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); + + class PresenceUtils { + Set visitedSourceNames = new LinkedHashSet<>(); + Set visitedTargetNames = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + Set visitedTargets = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + void map(EmployeeDto employeeDto, + @MappingTarget Employee employee, + @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @MappingTarget DomainModel target, + @SourcePropertyName String sourcePropName, + @TargetPropertyName String targetPropName, + @Context PresenceUtils utils) { + utils.visitedSourceNames.add( sourcePropName ); + utils.visitedTargetNames.add( targetPropName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + utils.visitedTargets.add( target.getClass().getSimpleName() ); + if ( targetPropName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java new file mode 100644 index 0000000000..d5dc378f32 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.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.test.conditional.propertyname.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodInMapperWithTargetPropertyName { + + ConditionalMethodInMapperWithTargetPropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithTargetPropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @TargetPropertyName String propName) { + if ( propName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java new file mode 100644 index 0000000000..381b99d4f4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.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.ap.test.conditional.propertyname.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@Mapper(uses = ConditionalMethodInUsesMapperWithTargetPropertyName.PresenceUtils.class) +public interface ConditionalMethodInUsesMapperWithTargetPropertyName { + + ConditionalMethodInUsesMapperWithTargetPropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInUsesMapperWithTargetPropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + class PresenceUtils { + + @Condition + public boolean isNotBlank(String value, @TargetPropertyName String propName) { + if ( propName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java new file mode 100644 index 0000000000..85704b0601 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java @@ -0,0 +1,106 @@ +/* + * 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.test.conditional.propertyname.targetpropertyname; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + */ +@Mapper +public interface ConditionalMethodWithTargetPropertyNameInContextMapper { + + ConditionalMethodWithTargetPropertyNameInContextMapper INSTANCE + = Mappers.getMapper( ConditionalMethodWithTargetPropertyNameInContextMapper.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + Address map(AddressDto addressDto, @Context PresenceUtils utils); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean isNotBlank(String value, @TargetPropertyName String propName) { + visited.add( propName ); + return value != null && !value.trim().isEmpty(); + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); + + class PresenceUtilsAllProps { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean collect(@TargetPropertyName String propName) { + visited.add( propName ); + return true; + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); + + @BeforeMapping + default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource utils) { + String lastProp = utils.visitedSegments.peekLast(); + if ( lastProp != null && source != null ) { + utils.path.offerLast( lastProp ); + } + } + + @AfterMapping + default void after(@TargetType Class targetClass, @Context PresenceUtilsAllPropsWithSource utils) { + // intermediate method for collection mapping must not change the path + if ( targetClass != List.class ) { + utils.path.pollLast(); + } + } + + class PresenceUtilsAllPropsWithSource { + Deque visitedSegments = new LinkedList<>(); + Deque visited = new LinkedList<>(); + Deque path = new LinkedList<>(); + + @Condition + public boolean collect(@TargetPropertyName String propName) { + visitedSegments.offerLast( propName ); + path.offerLast( propName ); + visited.offerLast( String.join( ".", path ) ); + path.pollLast(); + return true; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java new file mode 100644 index 0000000000..d56277abf4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.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.ap.test.conditional.propertyname.targetpropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; + +@Mapper +public interface ErroneousNonStringTargetPropertyNameParameter { + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @TargetPropertyName int propName) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java new file mode 100644 index 0000000000..bb90c0b069 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java @@ -0,0 +1,311 @@ +/* + * 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.test.conditional.propertyname.targetpropertyname; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + */ +@IssueKey("2051") +@WithClasses({ + Address.class, + AddressDto.class, + Employee.class, + EmployeeDto.class, + DomainModel.class +}) +public class TargetPropertyNameTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithTargetPropertyName.class + }) + public void conditionalMethodInMapperWithTargetPropertyName() { + ConditionalMethodInMapperWithTargetPropertyName mapper + = ConditionalMethodInMapperWithTargetPropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForCollectionMapperWithTargetPropertyName.class + }) + public void conditionalMethodForCollectionMapperWithTargetPropertyName() { + ConditionalMethodForCollectionMapperWithTargetPropertyName mapper + = ConditionalMethodForCollectionMapperWithTargetPropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInUsesMapperWithTargetPropertyName.class + }) + public void conditionalMethodInUsesMapperWithTargetPropertyName() { + ConditionalMethodInUsesMapperWithTargetPropertyName mapper + = ConditionalMethodInUsesMapperWithTargetPropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllOptions.class + }) + public void conditionalMethodInMapperWithAllOptions() { + ConditionalMethodInMapperWithAllOptions mapper + = ConditionalMethodInMapperWithAllOptions.INSTANCE; + + ConditionalMethodInMapperWithAllOptions.PresenceUtils utils = + new ConditionalMethodInMapperWithAllOptions.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = new Employee(); + mapper.map( employeeDto, employee, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visitedSourceNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" ); + assertThat( utils.visitedTargetNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); + assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); + assertThat( utils.visitedTargets ).containsExactly( "Employee" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllExceptTarget.class + }) + public void conditionalMethodInMapperWithAllExceptTarget() { + ConditionalMethodInMapperWithAllExceptTarget mapper + = ConditionalMethodInMapperWithAllExceptTarget.INSTANCE; + + ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils utils = + new ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isEqualTo( " " ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country", "street" ); + assertThat( utils.visitedSources ).containsExactlyInAnyOrder( "EmployeeDto", "AddressDto" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithTargetPropertyNameInContextMapper.class + }) + public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() { + ConditionalMethodWithTargetPropertyNameInContextMapper mapper + = ConditionalMethodWithTargetPropertyNameInContextMapper.INSTANCE; + + ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtils utils = + new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setLastName( " " ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country", "street" ); + + ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllProps allPropsUtils = + new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllProps(); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtils ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( allPropsUtils.visited ) + .containsExactlyInAnyOrder( + "firstName", + "lastName", + "title", + "country", + "active", + "age", + "boss", + "primaryAddress", + "addresses", + "street" + ); + + ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllPropsWithSource allPropsUtilsWithSource = + new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllPropsWithSource(); + + EmployeeDto bossEmployeeDto = new EmployeeDto(); + bossEmployeeDto.setLastName( "Boss Tester" ); + bossEmployeeDto.setOriginCountry( "US" ); + bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( + "Testing St. 10" ) ) ); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setBoss( bossEmployeeDto ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtilsWithSource ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ).isNotEmpty(); + assertThat( employee.getAddresses().get( 0 ).getStreet() ).isEqualTo( "Testing St. 6" ); + assertThat( employee.getBoss() ).isNotNull(); + assertThat( employee.getBoss().getCountry() ).isEqualTo( "US" ); + assertThat( employee.getBoss().getLastName() ).isEqualTo( "Boss Tester" ); + assertThat( employee.getBoss().getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 10" ); + assertThat( allPropsUtilsWithSource.visited ) + .containsExactly( + "country", + "addresses", + "addresses.street", + "firstName", + "lastName", + "title", + "active", + "age", + "boss", + "boss.country", + "boss.addresses", + "boss.addresses.street", + "boss.firstName", + "boss.lastName", + "boss.title", + "boss.active", + "boss.age", + "boss.boss", + "boss.primaryAddress", + "primaryAddress" + ); + } + + @IssueKey("2863") + @ProcessorTest + @WithClasses({ + ErroneousNonStringTargetPropertyNameParameter.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousNonStringTargetPropertyNameParameter.class, + line = 23, + message = "@TargetPropertyName can only by applied to a String parameter." + ) + } + ) + public void nonStringTargetPropertyNameParameter() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithClassQualifiersMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithClassQualifiersMapper.java new file mode 100644 index 0000000000..7cbef82a5a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithClassQualifiersMapper.java @@ -0,0 +1,63 @@ +/* + * 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.test.conditional.qualifier; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.Qualifier; +import org.mapstruct.ap.test.conditional.Employee; +import org.mapstruct.ap.test.conditional.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = ConditionalMethodWithClassQualifiersMapper.StaticUtil.class) +public interface ConditionalMethodWithClassQualifiersMapper { + + ConditionalMethodWithClassQualifiersMapper INSTANCE = + Mappers.getMapper( ConditionalMethodWithClassQualifiersMapper.class ); + + @Mapping(target = "ssid", source = "uniqueIdNumber", + conditionQualifiedBy = UtilConditions.class, conditionQualifiedByName = "american") + @Mapping(target = "nin", source = "uniqueIdNumber", + conditionQualifiedBy = UtilConditions.class, conditionQualifiedByName = "british") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + + @UtilConditions + interface StaticUtil { + + @Condition + @Named("american") + static boolean isAmericanCitizen(EmployeeDto employerDto) { + return "US".equals( employerDto.getCountry() ); + } + + @Condition + @Named("british") + static boolean isBritishCitizen(EmployeeDto employeeDto) { + return "UK".equals( employeeDto.getCountry() ); + } + } + + @Qualifier + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.CLASS) + @interface UtilConditions { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceParameterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceParameterMapper.java new file mode 100644 index 0000000000..c04269d574 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceParameterMapper.java @@ -0,0 +1,48 @@ +/* + * 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.test.conditional.qualifier; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.ap.test.conditional.Employee; +import org.mapstruct.ap.test.conditional.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = ConditionalMethodWithSourceParameterMapper.StaticUtil.class) +public interface ConditionalMethodWithSourceParameterMapper { + + ConditionalMethodWithSourceParameterMapper INSTANCE = + Mappers.getMapper( ConditionalMethodWithSourceParameterMapper.class ); + + @Mapping(target = "ssid", source = "uniqueIdNumber", conditionQualifiedByName = "isAmericanCitizen") + @Mapping(target = "nin", source = "uniqueIdNumber", conditionQualifiedByName = "isBritishCitizen") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + + @Condition + @Named("isAmericanCitizen") + default boolean isAmericanCitizen(EmployeeDto employerDto) { + return "US".equals( employerDto.getCountry() ); + } + + interface StaticUtil { + + @Condition + @Named("isBritishCitizen") + static boolean isBritishCitizen(EmployeeDto employeeDto) { + return "UK".equals( employeeDto.getCountry() ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java new file mode 100644 index 0000000000..6577a6fd95 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalMethodWithSourceToTargetMapper.java @@ -0,0 +1,132 @@ +/* + * 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.test.conditional.qualifier; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.SourceParameterCondition; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConditionalMethodWithSourceToTargetMapper { + + ConditionalMethodWithSourceToTargetMapper INSTANCE = + Mappers.getMapper( ConditionalMethodWithSourceToTargetMapper.class ); + + @Mapping(source = "orderDTO", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder") + Order convertToOrder(OrderDTO orderDTO); + + @Mapping(source = "customerName", target = "name") + @Mapping(source = "orderDTO", target = "address", conditionQualifiedByName = "mapAddressFromOrder") + Customer convertToCustomer(OrderDTO orderDTO); + + Address convertToAddress(OrderDTO orderDTO); + + @SourceParameterCondition + @Named("mapCustomerFromOrder") + default boolean mapCustomerFromOrder(OrderDTO orderDTO) { + return orderDTO != null && ( orderDTO.getCustomerName() != null || mapAddressFromOrder( orderDTO ) ); + } + + @SourceParameterCondition + @Named("mapAddressFromOrder") + default boolean mapAddressFromOrder(OrderDTO orderDTO) { + return orderDTO != null && ( orderDTO.getLine1() != null || orderDTO.getLine2() != null ); + } + + class OrderDTO { + + private String customerName; + private String line1; + private String line2; + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + + } + + class Order { + + private Customer customer; + + public Customer getCustomer() { + return customer; + } + + public void setCustomer(Customer customer) { + this.customer = customer; + } + } + + class Customer { + private String name; + private Address address; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Address getAddress() { + return address; + } + + public void setAddress(Address address) { + this.address = address; + } + } + + class Address { + + private String line1; + private String line2; + + public String getLine1() { + return line1; + } + + public void setLine1(String line1) { + this.line1 = line1; + } + + public String getLine2() { + return line2; + } + + public void setLine2(String line2) { + this.line2 = line2; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalQualifierTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalQualifierTest.java new file mode 100644 index 0000000000..69c9a048cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/qualifier/ConditionalQualifierTest.java @@ -0,0 +1,126 @@ +/* + * 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.test.conditional.qualifier; + +import org.mapstruct.ap.test.conditional.Employee; +import org.mapstruct.ap.test.conditional.EmployeeDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2051") +@WithClasses({ + Employee.class, + EmployeeDto.class +}) +public class ConditionalQualifierTest { + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourceParameterMapper.class + }) + public void conditionalMethodWithSourceParameter() { + ConditionalMethodWithSourceParameterMapper mapper = ConditionalMethodWithSourceParameterMapper.INSTANCE; + + EmployeeDto dto = new EmployeeDto(); + dto.setName( "Tester" ); + dto.setUniqueIdNumber( "SSID-001" ); + dto.setCountry( null ); + + Employee employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isNull(); + + dto.setCountry( "UK" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isEqualTo( "SSID-001" ); + assertThat( employee.getSsid() ).isNull(); + + dto.setCountry( "US" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isEqualTo( "SSID-001" ); + + dto.setCountry( "CH" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithClassQualifiersMapper.class + }) + public void conditionalClassQualifiers() { + ConditionalMethodWithClassQualifiersMapper mapper = ConditionalMethodWithClassQualifiersMapper.INSTANCE; + + EmployeeDto dto = new EmployeeDto(); + dto.setName( "Tester" ); + dto.setUniqueIdNumber( "SSID-001" ); + dto.setCountry( null ); + + Employee employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isNull(); + + dto.setCountry( "UK" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isEqualTo( "SSID-001" ); + assertThat( employee.getSsid() ).isNull(); + + dto.setCountry( "US" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isEqualTo( "SSID-001" ); + + dto.setCountry( "CH" ); + employee = mapper.map( dto ); + assertThat( employee.getNin() ).isNull(); + assertThat( employee.getSsid() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourceToTargetMapper.class + }) + @IssueKey("2666") + public void conditionalQualifiersForSourceToTarget() { + ConditionalMethodWithSourceToTargetMapper mapper = ConditionalMethodWithSourceToTargetMapper.INSTANCE; + + ConditionalMethodWithSourceToTargetMapper.OrderDTO orderDto = + new ConditionalMethodWithSourceToTargetMapper.OrderDTO(); + + ConditionalMethodWithSourceToTargetMapper.Order order = mapper.convertToOrder( orderDto ); + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNull(); + + orderDto = new ConditionalMethodWithSourceToTargetMapper.OrderDTO(); + orderDto.setCustomerName( "Tester" ); + order = mapper.convertToOrder( orderDto ); + + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNotNull(); + assertThat( order.getCustomer().getName() ).isEqualTo( "Tester" ); + assertThat( order.getCustomer().getAddress() ).isNull(); + + orderDto = new ConditionalMethodWithSourceToTargetMapper.OrderDTO(); + orderDto.setLine1( "Line 1" ); + order = mapper.convertToOrder( orderDto ); + + assertThat( order ).isNotNull(); + assertThat( order.getCustomer() ).isNotNull(); + assertThat( order.getCustomer().getName() ).isNull(); + assertThat( order.getCustomer().getAddress() ).isNotNull(); + assertThat( order.getCustomer().getAddress().getLine1() ).isEqualTo( "Line 1" ); + assertThat( order.getCustomer().getAddress().getLine2() ).isNull(); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/ConstructorProperties.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/ConstructorProperties.java new file mode 100644 index 0000000000..affbded7ff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/ConstructorProperties.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.ap.test.constructor; + +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 ConstructorProperties { + + String[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/Default.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/Default.java new file mode 100644 index 0000000000..96b30c02ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/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.ap.test.constructor; + +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/processor/src/test/java/org/mapstruct/ap/test/constructor/Person.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/Person.java new file mode 100644 index 0000000000..8fc9e7ce30 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/Person.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.test.constructor; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Person { + + private final String name; + private final int age; + private final String job; + private final String city; + private final String address; + private final List children; + + public Person(String name, int age, String job, String city, String address, + List children) { + this.name = name; + this.age = age; + this.job = job; + this.city = city; + this.address = address; + this.children = children; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public String getJob() { + return job; + } + + public String getCity() { + return city; + } + + public String getAddress() { + return address; + } + + public List getChildren() { + return children; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/PersonDto.java new file mode 100644 index 0000000000..0b2cba09cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/PersonDto.java @@ -0,0 +1,69 @@ +/* + * 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.test.constructor; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class PersonDto { + + private String name; + private int age; + private String job; + private String city; + private String address; + private List children; + + 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 String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/PersonWithConstructorProperties.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/PersonWithConstructorProperties.java new file mode 100644 index 0000000000..8419442cf1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/PersonWithConstructorProperties.java @@ -0,0 +1,58 @@ +/* + * 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.test.constructor.constructorproperties; + +import java.util.List; + +import org.mapstruct.ap.test.constructor.ConstructorProperties; + +/** + * @author Filip Hrisafov + */ +public class PersonWithConstructorProperties { + + private final String name; + private final int age; + private final String job; + private final String city; + private final String address; + private final List children; + + @ConstructorProperties({"name", "age", "job", "city", "address", "children"}) + public PersonWithConstructorProperties(String var1, int var2, String var3, String var4, String var5, + List var6) { + this.name = var1; + this.age = var2; + this.job = var3; + this.city = var4; + this.address = var5; + this.children = var6; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + + public String getJob() { + return job; + } + + public String getCity() { + return city; + } + + public String getAddress() { + return address; + } + + public List getChildren() { + return children; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesMapper.java new file mode 100644 index 0000000000..3d32d8b31c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesMapper.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.test.constructor.constructorproperties; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleConstructorPropertiesMapper { + + SimpleConstructorPropertiesMapper INSTANCE = Mappers.getMapper( SimpleConstructorPropertiesMapper.class ); + + PersonWithConstructorProperties map(PersonDto dto); + + @Mapping(target = "age", constant = "25") + @Mapping(target = "job", constant = "Software Developer") + PersonWithConstructorProperties mapWithConstants(PersonDto dto); + + @Mapping(target = "age", expression = "java(25 - 5)") + @Mapping(target = "job", expression = "java(\"Software Developer\".toLowerCase())") + PersonWithConstructorProperties mapWithExpression(PersonDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesTest.java new file mode 100644 index 0000000000..3e26b442ee --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/constructorproperties/SimpleConstructorPropertiesTest.java @@ -0,0 +1,87 @@ +/* + * 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.test.constructor.constructorproperties; + +import java.util.Arrays; + +import org.mapstruct.ap.test.constructor.ConstructorProperties; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + ConstructorProperties.class, + PersonWithConstructorProperties.class, + PersonDto.class, + SimpleConstructorPropertiesMapper.class +}) +public class SimpleConstructorPropertiesTest { + + @ProcessorTest + public void mapDefault() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + PersonWithConstructorProperties target = SimpleConstructorPropertiesMapper.INSTANCE.map( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 30 ); + assertThat( target.getJob() ).isEqualTo( "Software Engineer" ); + assertThat( target.getCity() ).isEqualTo( "Zurich" ); + assertThat( target.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" ); + } + + @ProcessorTest + public void mapWithConstants() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + PersonWithConstructorProperties target = SimpleConstructorPropertiesMapper.INSTANCE.mapWithConstants( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 25 ); + assertThat( target.getJob() ).isEqualTo( "Software Developer" ); + assertThat( target.getCity() ).isEqualTo( "Zurich" ); + assertThat( target.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" ); + } + + @ProcessorTest + public void mapWithExpressions() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + PersonWithConstructorProperties target = SimpleConstructorPropertiesMapper.INSTANCE.mapWithExpression( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 20 ); + assertThat( target.getJob() ).isEqualTo( "software developer" ); + assertThat( target.getCity() ).isEqualTo( "Zurich" ); + assertThat( target.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/PersonWithDefaultAnnotatedConstructor.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/PersonWithDefaultAnnotatedConstructor.java new file mode 100644 index 0000000000..190ab818ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/PersonWithDefaultAnnotatedConstructor.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.test.constructor.defaultannotated; + +import org.mapstruct.ap.test.constructor.Default; + +/** + * @author Filip Hrisafov + */ +public class PersonWithDefaultAnnotatedConstructor { + + private final String name; + private final int age; + + public PersonWithDefaultAnnotatedConstructor(String name) { + this( name, -1 ); + } + + @Default + public PersonWithDefaultAnnotatedConstructor(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorMapper.java new file mode 100644 index 0000000000..6934c34fe4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorMapper.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.ap.test.constructor.defaultannotated; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleDefaultAnnotatedConstructorMapper { + + SimpleDefaultAnnotatedConstructorMapper INSTANCE = + Mappers.getMapper( SimpleDefaultAnnotatedConstructorMapper.class ); + + PersonWithDefaultAnnotatedConstructor map(PersonDto dto); + + @Mapping(target = "age", constant = "25") + PersonWithDefaultAnnotatedConstructor mapWithConstants(PersonDto dto); + + @Mapping(target = "age", expression = "java(25 - 5)") + PersonWithDefaultAnnotatedConstructor mapWithExpression(PersonDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorTest.java new file mode 100644 index 0000000000..25f4fb8b1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/defaultannotated/SimpleDefaultAnnotatedConstructorTest.java @@ -0,0 +1,77 @@ +/* + * 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.test.constructor.defaultannotated; + +import java.util.Arrays; + +import org.mapstruct.ap.test.constructor.Default; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Default.class, + PersonWithDefaultAnnotatedConstructor.class, + PersonDto.class, + SimpleDefaultAnnotatedConstructorMapper.class +}) +public class SimpleDefaultAnnotatedConstructorTest { + + @ProcessorTest + public void mapDefault() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + PersonWithDefaultAnnotatedConstructor target = SimpleDefaultAnnotatedConstructorMapper.INSTANCE.map( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 30 ); + } + + @ProcessorTest + public void mapWithConstants() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + PersonWithDefaultAnnotatedConstructor target = + SimpleDefaultAnnotatedConstructorMapper.INSTANCE.mapWithConstants( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 25 ); + } + + @ProcessorTest + public void mapWithExpressions() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + PersonWithDefaultAnnotatedConstructor target = + SimpleDefaultAnnotatedConstructorMapper.INSTANCE.mapWithExpression( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 20 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousAmbiguousConstructorsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousAmbiguousConstructorsMapper.java new file mode 100644 index 0000000000..afa37ea77b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousAmbiguousConstructorsMapper.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.ap.test.constructor.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.constructor.PersonDto; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousAmbiguousConstructorsMapper { + + PersonWithMultipleConstructors map(PersonDto dto); + + class PersonWithMultipleConstructors { + + private final String name; + private final int age; + + public PersonWithMultipleConstructors(String name) { + this( name, -1 ); + } + + public PersonWithMultipleConstructors(String name, int age) { + this.name = name; + this.age = age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorPropertiesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorPropertiesMapper.java new file mode 100644 index 0000000000..930db7f8a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorPropertiesMapper.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.ap.test.constructor.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.constructor.ConstructorProperties; +import org.mapstruct.ap.test.constructor.PersonDto; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousConstructorPropertiesMapper { + + PersonWithIncorrectConstructorProperties map(PersonDto dto); + + class PersonWithIncorrectConstructorProperties { + + private final String name; + private final int age; + + @ConstructorProperties({ "name" }) + public PersonWithIncorrectConstructorProperties(String name, int age) { + this.name = name; + this.age = age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java new file mode 100644 index 0000000000..5a8483f23b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/erroneous/ErroneousConstructorTest.java @@ -0,0 +1,58 @@ +/* + * 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.test.constructor.erroneous; + +import org.mapstruct.ap.test.constructor.ConstructorProperties; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +public class ErroneousConstructorTest { + + @ProcessorTest + @WithClasses({ + ErroneousAmbiguousConstructorsMapper.class, + PersonDto.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic( + type = ErroneousAmbiguousConstructorsMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Ambiguous constructors found for creating org.mapstruct.ap.test.constructor.erroneous" + + ".ErroneousAmbiguousConstructorsMapper.PersonWithMultipleConstructors: " + + "PersonWithMultipleConstructors(java.lang.String), " + + "PersonWithMultipleConstructors(java.lang.String, int). Either declare parameterless constructor " + + "or annotate the default constructor with an annotation named @Default." + ) + }) + public void shouldUseMultipleConstructors() { + } + + @ProcessorTest + @WithClasses({ + ConstructorProperties.class, + ErroneousConstructorPropertiesMapper.class, + PersonDto.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { + @Diagnostic( + type = ErroneousConstructorPropertiesMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 18, + message = "Incorrect @ConstructorProperties for org.mapstruct.ap.test.constructor.erroneous" + + ".ErroneousConstructorPropertiesMapper.PersonWithIncorrectConstructorProperties. The size of the " + + "@ConstructorProperties does not match the number of constructor parameters") + }) + public void shouldNotCompileIfConstructorPropertiesDoesNotMatch() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/manysourcearguments/ManySourceArgumentsConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/manysourcearguments/ManySourceArgumentsConstructorMapper.java new file mode 100644 index 0000000000..dfa3b4be5c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/manysourcearguments/ManySourceArgumentsConstructorMapper.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.ap.test.constructor.manysourcearguments; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.constructor.Person; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface ManySourceArgumentsConstructorMapper { + + ManySourceArgumentsConstructorMapper INSTANCE = Mappers.getMapper( ManySourceArgumentsConstructorMapper.class ); + + @Mapping(target = "name", defaultValue = "Tester") + @Mapping(target = "city", defaultExpression = "java(\"Zurich\")") + Person map(String name, String city); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/manysourcearguments/ManySourceArgumentsConstructorMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/manysourcearguments/ManySourceArgumentsConstructorMappingTest.java new file mode 100644 index 0000000000..7611cc2364 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/manysourcearguments/ManySourceArgumentsConstructorMappingTest.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.test.constructor.manysourcearguments; + +import org.mapstruct.ap.test.constructor.Person; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses( { + ManySourceArgumentsConstructorMapper.class, + Person.class, +} ) +public class ManySourceArgumentsConstructorMappingTest { + + @ProcessorTest + public void shouldCorrectlyUseDefaultValueForSourceParameters() { + Person person = ManySourceArgumentsConstructorMapper.INSTANCE.map( null, "Test Valley" ); + + assertThat( person ).isNotNull(); + assertThat( person.getName() ).isEqualTo( "Tester" ); + assertThat( person.getCity() ).isEqualTo( "Test Valley" ); + + person = ManySourceArgumentsConstructorMapper.INSTANCE.map( "Other Tester", null ); + + assertThat( person ).isNotNull(); + assertThat( person.getName() ).isEqualTo( "Other Tester" ); + assertThat( person.getCity() ).isEqualTo( "Zurich" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersMapper.java new file mode 100644 index 0000000000..02a6d9af64 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersMapper.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.ap.test.constructor.mixed; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ConstructorMixedWithSettersMapper { + + ConstructorMixedWithSettersMapper INSTANCE = Mappers.getMapper( ConstructorMixedWithSettersMapper.class ); + + PersonMixed map(PersonDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersTest.java new file mode 100644 index 0000000000..dde9b798f4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/ConstructorMixedWithSettersTest.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.ap.test.constructor.mixed; + +import java.util.Arrays; + +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + PersonMixed.class, + PersonDto.class, + ConstructorMixedWithSettersMapper.class +}) +public class ConstructorMixedWithSettersTest { + + @ProcessorTest + public void mapDefault() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + PersonMixed target = ConstructorMixedWithSettersMapper.INSTANCE.map( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 30 ); + assertThat( target.getJob() ).isEqualTo( "Software Engineer" ); + assertThat( target.getCity() ).isEqualTo( "Zurich" ); + assertThat( target.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/PersonMixed.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/PersonMixed.java new file mode 100644 index 0000000000..deb79e84fc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/mixed/PersonMixed.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.ap.test.constructor.mixed; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class PersonMixed { + + private final String name; + private final int age; + private final String job; + private String city; + private String address; + private List children; + + public PersonMixed(String name, int age, String job) { + this.name = name; + this.age = age; + this.job = job; + } + + public String getName() { + return name; + } + + public void setName(String name) { + throw new RuntimeException( "Method is here only to verify that MapStruct won't use it" ); + } + + public int getAge() { + return age; + } + + public String getJob() { + return job; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntry.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntry.java new file mode 100644 index 0000000000..02a82ba8d0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntry.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.ap.test.constructor.nestedsource; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource.source.Chart; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface ArtistToChartEntry { + + ArtistToChartEntry MAPPER = Mappers.getMapper( ArtistToChartEntry.class ); + + @Mappings({ + @Mapping(target = "chartName", source = "chart.name"), + @Mapping(target = "songTitle", 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); + + @Mappings({ + @Mapping(target = "chartName", ignore = true), + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true) + }) + ChartEntry map(Song song); + + @Mappings({ + @Mapping(target = "chartName", source = "name"), + @Mapping(target = "songTitle", ignore = true), + @Mapping(target = "artistName", ignore = true), + @Mapping(target = "recordedAt", ignore = true), + @Mapping(target = "city", ignore = true), + @Mapping(target = "position", ignore = true) + }) + ChartEntry map(Chart name); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryComposedReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryComposedReverse.java new file mode 100644 index 0000000000..7fd9fbdb54 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryComposedReverse.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.ap.test.constructor.nestedsource; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryComposed; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryLabel; +import org.mapstruct.ap.test.constructor.nestedsource.source.Label; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryComposedReverse { + + public static final ArtistToChartEntryComposedReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryComposedReverse.class ); + + @Mappings({ + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "label", source = "artist.label"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true ) + }) + abstract ChartEntryComposed mapForward(Song song); + + @Mappings({ + @Mapping(target = "name", source = "name"), + @Mapping(target = "recordedAt", source = "studio.name"), + @Mapping(target = "city", source = "studio.city") + }) + abstract ChartEntryLabel mapForward(Label label); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntryComposed ce); + + @InheritInverseConfiguration + abstract Label mapReverse(ChartEntryLabel label); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryConfig.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryConfig.java new file mode 100644 index 0000000000..c1d0d05228 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryConfig.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.ap.test.constructor.nestedsource; + +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.constructor.nestedsource._target.BaseChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; + +/** + * + * @author Sjaak Derksen + */ +@MapperConfig( unmappedTargetPolicy = ReportingPolicy.ERROR ) +public interface ArtistToChartEntryConfig { + + @Mappings({ + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "chartName", ignore = true ) + }) + BaseChartEntry mapForwardConfig( Song song ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryReverse.java new file mode 100644 index 0000000000..193d4ea6ec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryReverse.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.test.constructor.nestedsource; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryReverse { + + public static final ArtistToChartEntryReverse MAPPER = Mappers.getMapper( ArtistToChartEntryReverse.class ); + + @Mappings({ + + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true ) + }) + abstract ChartEntry mapForward(Song song); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntry ce); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithConfigReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithConfigReverse.java new file mode 100644 index 0000000000..7c21104a6b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithConfigReverse.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.test.constructor.nestedsource; + +import org.mapstruct.InheritConfiguration; +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryWithBase; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper( config = ArtistToChartEntryConfig.class ) +public abstract class ArtistToChartEntryWithConfigReverse { + + public static final ArtistToChartEntryWithConfigReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryWithConfigReverse.class ); + + @InheritConfiguration + @Mappings({ + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true) + }) + abstract ChartEntryWithBase mapForward(Song song); + + @InheritInverseConfiguration( name = "mapForward" ) + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntryWithBase ce); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithFactoryReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithFactoryReverse.java new file mode 100644 index 0000000000..6583838673 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithFactoryReverse.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.ap.test.constructor.nestedsource; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryWithFactoryReverse { + + public static final ArtistToChartEntryWithFactoryReverse MAPPER + = Mappers.getMapper( ArtistToChartEntryWithFactoryReverse.class ); + + @Mappings({ + + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true) + }) + abstract ChartEntry mapForward(Song song); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntry ce); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithIgnoresReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithIgnoresReverse.java new file mode 100644 index 0000000000..4d1d35fbe6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithIgnoresReverse.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.ap.test.constructor.nestedsource; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryWithIgnoresReverse { + + public static final ArtistToChartEntryWithIgnoresReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryWithIgnoresReverse.class ); + + @Mappings({ + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistName", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true) + }) + abstract ChartEntry mapForward(Song song); + + @InheritInverseConfiguration + @Mappings({ + @Mapping(target = "positions", ignore = true), + @Mapping(target = "artist", ignore = true) + }) + abstract Song mapReverse(ChartEntry ce); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithMappingReverse.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithMappingReverse.java new file mode 100644 index 0000000000..b6b4cac803 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ArtistToChartEntryWithMappingReverse.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.ap.test.constructor.nestedsource; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryWithMapping; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ArtistToChartEntryWithMappingReverse { + + public static final ArtistToChartEntryWithMappingReverse MAPPER = + Mappers.getMapper( ArtistToChartEntryWithMappingReverse.class ); + + @Mappings({ + @Mapping(target = "songTitle", source = "title"), + @Mapping(target = "artistId", source = "artist.name"), + @Mapping(target = "recordedAt", source = "artist.label.studio.name"), + @Mapping(target = "city", source = "artist.label.studio.city"), + @Mapping(target = "position", ignore = true), + @Mapping(target = "chartName", ignore = true) + }) + abstract ChartEntryWithMapping mapForward(Song song); + + @InheritInverseConfiguration + @Mapping(target = "positions", ignore = true) + abstract Song mapReverse(ChartEntryWithMapping ce); + + int mapArtistToArtistId(String in) { + + if ( "The Beatles".equals( in ) ) { + return 1; + } + else { + return -1; + } + } + + String mapArtistIdToArtist(int in) { + + if ( in == 1 ) { + return "The Beatles"; + } + else { + return "Unknown"; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/NestedSourcePropertiesConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/NestedSourcePropertiesConstructorTest.java new file mode 100644 index 0000000000..653fccd118 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/NestedSourcePropertiesConstructorTest.java @@ -0,0 +1,129 @@ +/* + * 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.test.constructor.nestedsource; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource.source.Artist; +import org.mapstruct.ap.test.constructor.nestedsource.source.Chart; +import org.mapstruct.ap.test.constructor.nestedsource.source.Label; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.ap.test.constructor.nestedsource.source.Studio; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + */ +@WithClasses({ Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class }) +@IssueKey("73") +public class NestedSourcePropertiesConstructorTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ ArtistToChartEntry.class }) + public void shouldGenerateImplementationForPropertyNamesOnly() { + generatedSource.addComparisonToFixtureFor( ArtistToChartEntry.class ); + + Studio studio = new Studio( "Abbey Road", "London" ); + + Label label = new Label( "EMY", studio ); + + Artist artist = new Artist( "The Beatles", label ); + + Song song = new Song( artist, "A Hard Day's Night", Collections.emptyList() ); + + ChartEntry chartEntry = ArtistToChartEntry.MAPPER.map( song ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + } + + @ProcessorTest + @WithClasses({ ArtistToChartEntry.class }) + public void shouldGenerateImplementationForMultipleParam() { + + Studio studio = new Studio( "Abbey Road", "London" ); + + Label label = new Label( "EMY", studio ); + + Artist artist = new Artist( "The Beatles", label ); + + Song song = new Song( artist, "A Hard Day's Night", Collections.emptyList() ); + + Chart chart = new Chart( "record-sales", "Billboard", null ); + + ChartEntry chartEntry = ArtistToChartEntry.MAPPER.map( chart, song, 1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isEqualTo( "Billboard" ); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 1 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + chartEntry = ArtistToChartEntry.MAPPER.map( null, song, 10 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 10 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + chartEntry = ArtistToChartEntry.MAPPER.map( chart, null, 5 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isNull(); + assertThat( chartEntry.getChartName() ).isEqualTo( "Billboard" ); + assertThat( chartEntry.getCity() ).isNull(); + assertThat( chartEntry.getPosition() ).isEqualTo( 5 ); + assertThat( chartEntry.getRecordedAt() ).isNull(); + assertThat( chartEntry.getSongTitle() ).isNull(); + + chartEntry = ArtistToChartEntry.MAPPER.map( chart, song, null ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isEqualTo( "Billboard" ); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + } + + @ProcessorTest + @WithClasses({ ArtistToChartEntry.class }) + public void shouldPickPropertyNameOverParameterName() { + + Chart chart = new Chart( "record-sales", "Billboard", null ); + + ChartEntry chartEntry = ArtistToChartEntry.MAPPER.map( chart ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isNull(); + assertThat( chartEntry.getChartName() ).isEqualTo( "Billboard" ); + assertThat( chartEntry.getCity() ).isNull(); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isNull(); + assertThat( chartEntry.getSongTitle() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ReversingNestedSourcePropertiesConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ReversingNestedSourcePropertiesConstructorTest.java new file mode 100644 index 0000000000..73b23c5677 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/ReversingNestedSourcePropertiesConstructorTest.java @@ -0,0 +1,219 @@ +/* + * 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.test.constructor.nestedsource; + +import java.util.Collections; + +import org.mapstruct.ap.test.constructor.nestedsource._target.BaseChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryComposed; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryLabel; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryWithBase; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntryWithMapping; +import org.mapstruct.ap.test.constructor.nestedsource.source.Artist; +import org.mapstruct.ap.test.constructor.nestedsource.source.Chart; +import org.mapstruct.ap.test.constructor.nestedsource.source.Label; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.ap.test.constructor.nestedsource.source.Studio; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("73") +@WithClasses({ Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class }) +public class ReversingNestedSourcePropertiesConstructorTest { + + @ProcessorTest + @WithClasses({ ArtistToChartEntryReverse.class }) + public void shouldGenerateNestedReverse() { + + Song song1 = prepareSong(); + + ChartEntry chartEntry = ArtistToChartEntryReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + @ProcessorTest + @WithClasses({ ArtistToChartEntryWithIgnoresReverse.class }) + public void shouldIgnoreEverytingBelowArtist() { + + Song song1 = prepareSong(); + + ChartEntry chartEntry = ArtistToChartEntryWithIgnoresReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryWithIgnoresReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNull(); + } + + @ProcessorTest + @WithClasses({ ArtistToChartEntryWithFactoryReverse.class }) + public void shouldGenerateNestedReverseWithFactory() { + + Song song1 = prepareSong(); + + ChartEntry chartEntry = ArtistToChartEntryWithFactoryReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryWithFactoryReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + + } + + @ProcessorTest + @WithClasses({ ArtistToChartEntryComposedReverse.class, ChartEntryComposed.class, ChartEntryLabel.class }) + public void shouldGenerateNestedComposedReverse() { + + Song song1 = prepareSong(); + + ChartEntryComposed chartEntry = ArtistToChartEntryComposedReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getLabel().getName() ).isEqualTo( "EMY" ); + assertThat( chartEntry.getLabel().getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getLabel().getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryComposedReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isEqualTo( "EMY" ); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + @ProcessorTest + @WithClasses({ ArtistToChartEntryWithMappingReverse.class, ChartEntryWithMapping.class }) + public void shouldGenerateNestedWithMappingReverse() { + + Song song1 = prepareSong(); + + ChartEntryWithMapping chartEntry = ArtistToChartEntryWithMappingReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistId() ).isEqualTo( 1 ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryWithMappingReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + @ProcessorTest + @WithClasses({ + ArtistToChartEntryWithConfigReverse.class, + ArtistToChartEntryConfig.class, + BaseChartEntry.class, + ChartEntryWithBase.class + }) + public void shouldGenerateNestedWithConfigReverse() { + + Song song1 = prepareSong(); + + ChartEntryWithBase chartEntry = ArtistToChartEntryWithConfigReverse.MAPPER.mapForward( song1 ); + + assertThat( chartEntry ).isNotNull(); + assertThat( chartEntry.getArtistName() ).isEqualTo( "The Beatles" ); + assertThat( chartEntry.getChartName() ).isNull(); + assertThat( chartEntry.getCity() ).isEqualTo( "London" ); + assertThat( chartEntry.getPosition() ).isEqualTo( 0 ); + assertThat( chartEntry.getRecordedAt() ).isEqualTo( "Abbey Road" ); + assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); + + // and now in reverse + Song song2 = ArtistToChartEntryWithConfigReverse.MAPPER.mapReverse( chartEntry ); + + assertThat( song2 ).isNotNull(); + assertThat( song2.getArtist() ).isNotNull(); + assertThat( song2.getArtist().getName() ).isEqualTo( "The Beatles" ); + assertThat( song2.getArtist().getLabel() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getName() ).isNull(); + assertThat( song2.getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( song2.getArtist().getLabel().getStudio().getCity() ).isEqualTo( "London" ); + assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); + } + + private Song prepareSong() { + Studio studio = new Studio( "Abbey Road", "London" ); + + Label label = new Label( "EMY", studio ); + + Artist artist = new Artist( "The Beatles", label ); + + return new Song( artist, "A Hard Day's Night", Collections.emptyList() ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/AdderUsageObserver.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/AdderUsageObserver.java new file mode 100644 index 0000000000..ff21870b75 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/AdderUsageObserver.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.ap.test.constructor.nestedsource._target; + +/** + * @author Sjaak Derksen + */ +public class AdderUsageObserver { + + private AdderUsageObserver() { + } + + private static boolean used = false; + + public static boolean isUsed() { + return used; + } + + public static void setUsed(boolean used) { + AdderUsageObserver.used = used; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/BaseChartEntry.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/BaseChartEntry.java new file mode 100644 index 0000000000..7b82c77b32 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/BaseChartEntry.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.test.constructor.nestedsource._target; + +/** + * @author Filip Hrisafov + */ +public class BaseChartEntry { + + private final String chartName; + private final String songTitle; + private final String artistName; + + public BaseChartEntry(String chartName, String songTitle, String artistName) { + this.chartName = chartName; + this.songTitle = songTitle; + this.artistName = artistName; + } + + public String getChartName() { + return chartName; + } + + public String getSongTitle() { + return songTitle; + } + + public String getArtistName() { + return artistName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntry.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntry.java new file mode 100644 index 0000000000..36d7e95601 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntry.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.ap.test.constructor.nestedsource._target; + +/** + * @author Filip Hrisafov + */ +public class ChartEntry { + + private final String chartName; + private final String songTitle; + private final String artistName; + private final String recordedAt; + private final String city; + private final int position; + + public ChartEntry(String chartName, String songTitle, String artistName, String recordedAt, String city, + int position) { + this.chartName = chartName; + this.songTitle = songTitle; + this.artistName = artistName; + this.recordedAt = recordedAt; + this.city = city; + this.position = position; + } + + public String getChartName() { + return chartName; + } + + public String getSongTitle() { + return songTitle; + } + + public String getArtistName() { + return artistName; + } + + public String getRecordedAt() { + return recordedAt; + } + + public String getCity() { + return city; + } + + public int getPosition() { + return position; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryComposed.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryComposed.java new file mode 100644 index 0000000000..2543c70686 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryComposed.java @@ -0,0 +1,58 @@ +/* + * 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.test.constructor.nestedsource._target; + +/** + * @author Filip Hrisafov + */ +public class ChartEntryComposed { + + private String chartName; + private String songTitle; + private String artistName; + private ChartEntryLabel label; + private int position; + + public String getChartName() { + return chartName; + } + + public void setChartName(String chartName) { + this.chartName = chartName; + } + + public String getSongTitle() { + return songTitle; + } + + public void setSongTitle(String songTitle) { + this.songTitle = songTitle; + } + + public String getArtistName() { + return artistName; + } + + public void setArtistName(String artistName) { + this.artistName = artistName; + } + + public ChartEntryLabel getLabel() { + return label; + } + + public void setLabel(ChartEntryLabel label) { + this.label = label; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryLabel.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryLabel.java new file mode 100644 index 0000000000..b639b97b35 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryLabel.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.test.constructor.nestedsource._target; + +/** + * @author Filip Hrisafov + */ +public class ChartEntryLabel { + + private final String name; + private final String city; + private final String recordedAt; + + public ChartEntryLabel(String name, String city, String recordedAt) { + this.name = name; + this.city = city; + this.recordedAt = recordedAt; + } + + public String getName() { + return name; + } + + public String getCity() { + return city; + } + + public String getRecordedAt() { + return recordedAt; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithBase.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithBase.java new file mode 100644 index 0000000000..4855080fb3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithBase.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.test.constructor.nestedsource._target; + +/** + * @author Filip Hrisafov + */ +public class ChartEntryWithBase extends BaseChartEntry { + + private final String recordedAt; + private final String city; + private final int position; + + public ChartEntryWithBase(String chartName, String songTitle, String artistName, String recordedAt, + String city, int position) { + super( chartName, songTitle, artistName ); + this.recordedAt = recordedAt; + this.city = city; + this.position = position; + } + + public String getRecordedAt() { + return recordedAt; + } + + public String getCity() { + return city; + } + + public int getPosition() { + return position; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithMapping.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithMapping.java new file mode 100644 index 0000000000..f7ba366e17 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartEntryWithMapping.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.ap.test.constructor.nestedsource._target; + +/** + * @author Filip Hrisafov + */ +public class ChartEntryWithMapping { + + private final String chartName; + private final String songTitle; + private final int artistId; + private final String recordedAt; + private final String city; + private final int position; + + public ChartEntryWithMapping(String chartName, String songTitle, int artistId, String recordedAt, + String city, int position) { + this.chartName = chartName; + this.songTitle = songTitle; + this.artistId = artistId; + this.recordedAt = recordedAt; + this.city = city; + this.position = position; + } + + public String getChartName() { + return chartName; + } + + public String getSongTitle() { + return songTitle; + } + + public int getArtistId() { + return artistId; + } + + public String getRecordedAt() { + return recordedAt; + } + + public String getCity() { + return city; + } + + public int getPosition() { + return position; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartPositions.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartPositions.java new file mode 100644 index 0000000000..a14e04ac76 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/_target/ChartPositions.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.ap.test.constructor.nestedsource._target; + +import java.util.Collections; +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class ChartPositions { + + private final List positions; + + public ChartPositions(List positions) { + this.positions = positions; + } + + public List getPositions() { + return Collections.unmodifiableList( positions ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Artist.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Artist.java new file mode 100644 index 0000000000..def4ac697f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Artist.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.ap.test.constructor.nestedsource.source; + +/** + * @author Filip Hrisafov + */ +public class Artist { + + private final String name; + private final Label label; + + public Artist(String name, Label label) { + this.name = name; + this.label = label; + } + + public String getName() { + return name; + } + + public Label getLabel() { + return label; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Chart.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Chart.java new file mode 100644 index 0000000000..064129fc06 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Chart.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.test.constructor.nestedsource.source; + +/** + * @author Filip Hrisafov + */ +public class Chart { + + private final String type; + private final String name; + private final Song song; + + public Chart(String type, String name, Song song) { + this.type = type; + this.name = name; + this.song = song; + } + + public String getType() { + return type; + } + + public String getName() { + return name; + } + + public Song getSong() { + return song; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Label.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Label.java new file mode 100644 index 0000000000..6c2a15bbfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Label.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.ap.test.constructor.nestedsource.source; + +/** + * @author Filip Hrisafov + */ +public class Label { + + private final String name; + private final Studio studio; + + public Label(String name, Studio studio) { + this.name = name; + this.studio = studio; + } + + public String getName() { + return name; + } + + public Studio getStudio() { + return studio; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Song.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Song.java new file mode 100644 index 0000000000..ac6ce20104 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Song.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.test.constructor.nestedsource.source; + +import java.util.List; + +/** + * @author Filip Hrisafov + */ +public class Song { + + private final Artist artist; + private final String title; + private final List positions; + + public Song(Artist artist, String title, List positions) { + this.artist = artist; + this.title = title; + this.positions = positions; + } + + public Artist getArtist() { + return artist; + } + + public String getTitle() { + return title; + } + + public List getPositions() { + return positions; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Studio.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Studio.java new file mode 100644 index 0000000000..db7bfd7389 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedsource/source/Studio.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.ap.test.constructor.nestedsource.source; + +/** + * @author Filip Hrisafov + */ +public class Studio { + + private final String name; + private final String city; + + public Studio(String name, String city) { + this.name = name; + this.city = city; + } + + public String getName() { + return name; + } + + public String getCity() { + return city; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtist.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtist.java new file mode 100644 index 0000000000..9c1946fa26 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/ChartEntryToArtist.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.ap.test.constructor.nestedtarget; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource.source.Chart; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public abstract class ChartEntryToArtist { + + public static final ChartEntryToArtist MAPPER = Mappers.getMapper( ChartEntryToArtist.class ); + + @Mappings({ + @Mapping(target = "type", ignore = true), + @Mapping(target = "name", source = "chartName"), + @Mapping(target = "song.title", source = "songTitle"), + @Mapping(target = "song.artist.name", source = "artistName"), + @Mapping(target = "song.artist.label.studio.name", source = "recordedAt"), + @Mapping(target = "song.artist.label.studio.city", source = "city"), + @Mapping(target = "song.positions", source = "position") + }) + public abstract Chart map(ChartEntry chartEntry); + + @InheritInverseConfiguration + public abstract ChartEntry map(Chart chart); + + protected List mapPosition(Integer in) { + if ( in != null ) { + return new ArrayList<>( Arrays.asList( in ) ); + } + else { + return new ArrayList<>(); + } + } + + protected Integer mapPosition(List in) { + if ( in != null && !in.isEmpty() ) { + return in.get( 0 ); + } + else { + return null; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/NestedProductPropertiesConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/NestedProductPropertiesConstructorTest.java new file mode 100644 index 0000000000..c5cbe8279c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/nestedtarget/NestedProductPropertiesConstructorTest.java @@ -0,0 +1,95 @@ +/* + * 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.test.constructor.nestedtarget; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.constructor.nestedsource._target.ChartEntry; +import org.mapstruct.ap.test.constructor.nestedsource.source.Artist; +import org.mapstruct.ap.test.constructor.nestedsource.source.Chart; +import org.mapstruct.ap.test.constructor.nestedsource.source.Label; +import org.mapstruct.ap.test.constructor.nestedsource.source.Song; +import org.mapstruct.ap.test.constructor.nestedsource.source.Studio; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + */ +@WithClasses({ + Song.class, + Artist.class, + Chart.class, + Label.class, + Studio.class, + ChartEntry.class, + ChartEntryToArtist.class, +}) +@IssueKey("73") +public class NestedProductPropertiesConstructorTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + ChartEntryToArtist.class + ); + + @ProcessorTest + public void shouldMapNestedTarget() { + + ChartEntry chartEntry = new ChartEntry( + "US Billboard Hot Rock Songs", + "Purple Rain", + "Prince", + "Live, First Avenue, Minneapolis", + "Minneapolis", + 1 + ); + + Chart result = ChartEntryToArtist.MAPPER.map( chartEntry ); + + assertThat( result.getName() ).isEqualTo( "US Billboard Hot Rock Songs" ); + assertThat( result.getSong() ).isNotNull(); + assertThat( result.getSong().getArtist() ).isNotNull(); + assertThat( result.getSong().getTitle() ).isEqualTo( "Purple Rain" ); + assertThat( result.getSong().getArtist().getName() ).isEqualTo( "Prince" ); + assertThat( result.getSong().getArtist().getLabel() ).isNotNull(); + assertThat( result.getSong().getArtist().getLabel().getStudio() ).isNotNull(); + assertThat( result.getSong().getArtist().getLabel().getStudio().getName() ) + .isEqualTo( "Live, First Avenue, Minneapolis" ); + assertThat( result.getSong().getArtist().getLabel().getStudio().getCity() ) + .isEqualTo( "Minneapolis" ); + assertThat( result.getSong().getPositions() ).hasSize( 1 ); + assertThat( result.getSong().getPositions().get( 0 ) ).isEqualTo( 1 ); + + } + + @ProcessorTest + public void shouldReverseNestedTarget() { + + ChartEntry chartEntry = new ChartEntry( + "US Billboard Hot Rock Songs", + "Purple Rain", + "Prince", + "Live, First Avenue, Minneapolis", + "Minneapolis", + 1 + ); + + Chart chart = ChartEntryToArtist.MAPPER.map( chartEntry ); + ChartEntry result = ChartEntryToArtist.MAPPER.map( chart ); + + assertThat( result ).isNotNull(); + assertThat( result.getArtistName() ).isEqualTo( "Prince" ); + assertThat( result.getChartName() ).isEqualTo( "US Billboard Hot Rock Songs" ); + assertThat( result.getCity() ).isEqualTo( "Minneapolis" ); + assertThat( result.getPosition() ).isEqualTo( 1 ); + assertThat( result.getRecordedAt() ).isEqualTo( "Live, First Avenue, Minneapolis" ); + assertThat( result.getSongTitle() ).isEqualTo( "Purple Rain" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorMapper.java new file mode 100644 index 0000000000..2e162d2f23 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorMapper.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.ap.test.constructor.simple; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.constructor.Person; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleConstructorMapper { + + SimpleConstructorMapper INSTANCE = Mappers.getMapper( SimpleConstructorMapper.class ); + + Person map(PersonDto dto); + + @Mapping(target = "age", constant = "25") + @Mapping(target = "job", constant = "Software Developer") + Person mapWithConstants(PersonDto dto); + + @Mapping(target = "age", expression = "java(25 - 5)") + @Mapping(target = "job", expression = "java(\"Software Developer\".toLowerCase())") + Person mapWithExpression(PersonDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorTest.java new file mode 100644 index 0000000000..49bb7e6d8a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/simple/SimpleConstructorTest.java @@ -0,0 +1,86 @@ +/* + * 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.test.constructor.simple; + +import java.util.Arrays; + +import org.mapstruct.ap.test.constructor.Person; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Person.class, + PersonDto.class, + SimpleConstructorMapper.class +}) +public class SimpleConstructorTest { + + @ProcessorTest + public void mapDefault() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + Person target = SimpleConstructorMapper.INSTANCE.map( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 30 ); + assertThat( target.getJob() ).isEqualTo( "Software Engineer" ); + assertThat( target.getCity() ).isEqualTo( "Zurich" ); + assertThat( target.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" ); + } + + @ProcessorTest + public void mapWithConstants() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + Person target = SimpleConstructorMapper.INSTANCE.mapWithConstants( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 25 ); + assertThat( target.getJob() ).isEqualTo( "Software Developer" ); + assertThat( target.getCity() ).isEqualTo( "Zurich" ); + assertThat( target.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" ); + } + + @ProcessorTest + public void mapWithExpressions() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + source.setJob( "Software Engineer" ); + source.setCity( "Zurich" ); + source.setAddress( "Plaza 1" ); + source.setChildren( Arrays.asList( "Alice", "Tom" ) ); + + Person target = SimpleConstructorMapper.INSTANCE.mapWithExpression( source ); + + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 20 ); + assertThat( target.getJob() ).isEqualTo( "software developer" ); + assertThat( target.getCity() ).isEqualTo( "Zurich" ); + assertThat( target.getAddress() ).isEqualTo( "Plaza 1" ); + assertThat( target.getChildren() ).containsExactly( "Alice", "Tom" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/Order.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/Order.java new file mode 100644 index 0000000000..18fe9b1015 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/Order.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.test.constructor.unmapped; + +import java.util.Date; + +/** + * @author Filip Hrisafov + */ +public class Order { + + private final String name; + private final Date time; + + public Order(String name, Date time) { + this.name = name; + this.time = time; + } + + public String getName() { + return name; + } + + public Date getTime() { + return time; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/OrderDto.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/OrderDto.java new file mode 100644 index 0000000000..259cf8973d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/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.ap.test.constructor.unmapped; + +/** + * @author Filip Hrisafov + */ +public class OrderDto { + + private final String name; + + public OrderDto(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorMapper.java new file mode 100644 index 0000000000..8fcede06f4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorMapper.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.ap.test.constructor.unmapped; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface UnmappedConstructorMapper { + + UnmappedConstructorMapper INSTANCE = Mappers.getMapper( UnmappedConstructorMapper.class ); + + Order map(OrderDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorTest.java new file mode 100644 index 0000000000..0e35361f48 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/unmapped/UnmappedConstructorTest.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.ap.test.constructor.unmapped; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Order.class, + OrderDto.class, + UnmappedConstructorMapper.class +}) +public class UnmappedConstructorTest { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = UnmappedConstructorMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 19, + message = "Unmapped target property: \"time\".") + }) + public void shouldGenerateCompilableCodeForUnmappedConstructorProperties() { + OrderDto source = new OrderDto( "truck" ); + + Order target = UnmappedConstructorMapper.INSTANCE.map( source ); + + assertThat( target.getName() ).isEqualTo( "truck" ); + assertThat( target.getTime() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java new file mode 100644 index 0000000000..5eebaf5d05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/ConstructorVisibilityTest.java @@ -0,0 +1,88 @@ +/* + * 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.test.constructor.visibility; + +import org.mapstruct.ap.test.constructor.Default; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2150") +@WithClasses({ + PersonDto.class, + Default.class, +}) +public class ConstructorVisibilityTest { + + @ProcessorTest + @WithClasses({ + SimpleWithPublicConstructorMapper.class + }) + public void shouldUseSinglePublicConstructorAlways() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + + SimpleWithPublicConstructorMapper.Person target = + SimpleWithPublicConstructorMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 30 ); + } + + @ProcessorTest + @WithClasses({ + SimpleWithPublicParameterlessConstructorMapper.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = SimpleWithPublicParameterlessConstructorMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "No target property found for target " + + "\"SimpleWithPublicParameterlessConstructorMapper.Person\"."), + }) + + public void shouldUsePublicParameterConstructorIfPresent() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + + SimpleWithPublicParameterlessConstructorMapper.Person target = + SimpleWithPublicParameterlessConstructorMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isEqualTo( "From Constructor" ); + assertThat( target.getAge() ).isEqualTo( -1 ); + } + + @ProcessorTest + @WithClasses({ + SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper.class + }) + public void shouldUseDefaultAnnotatedConstructorAlways() { + PersonDto source = new PersonDto(); + source.setName( "Bob" ); + source.setAge( 30 ); + + SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper.Person target = + SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isEqualTo( "Bob" ); + assertThat( target.getAge() ).isEqualTo( 30 ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicConstructorMapper.java new file mode 100644 index 0000000000..ce0a98aa34 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicConstructorMapper.java @@ -0,0 +1,48 @@ +/* + * 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.test.constructor.visibility; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleWithPublicConstructorMapper { + + SimpleWithPublicConstructorMapper INSTANCE = Mappers.getMapper( SimpleWithPublicConstructorMapper.class ); + + Person map(PersonDto dto); + + class Person { + + private final String name; + private final int age; + + protected Person() { + this( "From Constructor", -1 ); + } + + protected Person(String name) { + this( name, -1 ); + } + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper.java new file mode 100644 index 0000000000..923821681a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper.java @@ -0,0 +1,51 @@ +/* + * 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.test.constructor.visibility; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.constructor.Default; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper { + + SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper INSTANCE = Mappers.getMapper( + SimpleWithPublicParameterlessConstructorAndDefaultAnnotatedMapper.class ); + + Person map(PersonDto dto); + + class Person { + + private final String name; + private final int age; + + protected Person() { + this( "From Constructor", -1 ); + } + + protected Person(String name) { + this( name, -1 ); + } + + @Default + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicParameterlessConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicParameterlessConstructorMapper.java new file mode 100644 index 0000000000..c0ba14bc42 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/constructor/visibility/SimpleWithPublicParameterlessConstructorMapper.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.ap.test.constructor.visibility; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.constructor.PersonDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleWithPublicParameterlessConstructorMapper { + + SimpleWithPublicParameterlessConstructorMapper INSTANCE = Mappers.getMapper( + SimpleWithPublicParameterlessConstructorMapper.class ); + + Person map(PersonDto dto); + + class Person { + + private final String name; + private final int age; + + public Person() { + this( "From Constructor", -1 ); + } + + protected Person(String name) { + this( name, -1 ); + } + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithContext.java new file mode 100644 index 0000000000..21a93fab82 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithContext.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.ap.test.context; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper(uses = { CycleContextLifecycleMethods.class, FactoryContextMethods.class }) +public interface AutomappingNodeMapperWithContext { + + AutomappingNodeMapperWithContext INSTANCE = + Mappers.getMapper( AutomappingNodeMapperWithContext.class ); + + NodeDto nodeToNodeDto(Node node, @Context CycleContext cycleContext, @Context FactoryContext factoryContext); + + void nodeToNodeDto(Node node, @MappingTarget NodeDto nodeDto, @Context CycleContext cycleContext, + @Context FactoryContext factoryContext); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithSelfContainingContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithSelfContainingContext.java new file mode 100644 index 0000000000..1e59c275c6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/AutomappingNodeMapperWithSelfContainingContext.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.ap.test.context; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper(uses = FactoryContextMethods.class) +public interface AutomappingNodeMapperWithSelfContainingContext { + + AutomappingNodeMapperWithSelfContainingContext INSTANCE = + Mappers.getMapper( AutomappingNodeMapperWithSelfContainingContext.class ); + + NodeDto nodeToNodeDto(Node node, @Context SelfContainingCycleContext cycleContext, + @Context FactoryContext factoryContext); + + void nodeToNodeDto(Node node, @MappingTarget NodeDto nodeDto, @Context SelfContainingCycleContext cycleContext, + @Context FactoryContext factoryContext); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java new file mode 100644 index 0000000000..46bb08cb70 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.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.ap.test.context; + +import javax.tools.Diagnostic.Kind; + +import org.mapstruct.Context; +import org.mapstruct.ap.test.context.erroneous.ErroneousNodeMapperWithNonUniqueContextTypes; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * Tests the erroneous usage of the {@link Context} annotation in the following situations: + *
        + *
      • using the same context parameter type twice in the same method + *
      + * + * @author Andreas Gudian + */ +@IssueKey("975") +@WithClasses({ + Node.class, + NodeDto.class, + CycleContext.class }) +public class ContextParameterErroneousTest { + + @ProcessorTest + @WithClasses(ErroneousNodeMapperWithNonUniqueContextTypes.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = @Diagnostic( + kind = Kind.ERROR, + line = 20, + type = ErroneousNodeMapperWithNonUniqueContextTypes.class, + message = "The types of @Context parameters must be unique.")) + public void reportsNonUniqueContextParamType() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterTest.java b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterTest.java new file mode 100644 index 0000000000..017183743b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterTest.java @@ -0,0 +1,125 @@ +/* + * 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.test.context; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.ObjectFactory; +import org.mapstruct.ap.test.context.Node.Attribute; +import org.mapstruct.ap.test.context.NodeDto.AttributeDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests the usage of the {@link Context} annotation in the following situations: + *
        + *
      • passing the parameter to property mapping methods (create and update) + *
      • passing the parameter to forged iterable methods + *
      • passing the parameter to forged bean mapping methods + *
      • passing the parameter to factory methods (with and without {@link ObjectFactory}) + *
      • passing the parameter to lifecycle methods (in this case, {@link BeforeMapping} + *
      • passing multiple parameters, with varied order of context params and mapping source params + *
      • calling lifecycle methods on context params + *
      + * + * @author Andreas Gudian + */ +@IssueKey("975") +@WithClasses({ + Node.class, + NodeDto.class, + NodeMapperWithContext.class, + AutomappingNodeMapperWithContext.class, + AutomappingNodeMapperWithSelfContainingContext.class, + CycleContext.class, + FactoryContext.class, + CycleContextLifecycleMethods.class, + FactoryContextMethods.class, + SelfContainingCycleContext.class }) +public class ContextParameterTest { + + private static final int MAGIC_NUMBER_OFFSET = 10; + + @ProcessorTest + public void mappingWithContextCorrectlyResolvesCycles() { + Node root = buildNodes(); + NodeDto rootDto = + NodeMapperWithContext.INSTANCE.nodeToNodeDto( new FactoryContext( 0, 10 ), root, new CycleContext() ); + assertResult( rootDto ); + + NodeDto updated = new NodeDto( 0 ); + NodeMapperWithContext.INSTANCE.nodeToNodeDto( new FactoryContext( 1, 10 ), root, updated, new CycleContext() ); + assertResult( updated ); + } + + @ProcessorTest + public void automappingWithContextCorrectlyResolvesCycles() { + Node root = buildNodes(); + NodeDto rootDto = AutomappingNodeMapperWithContext.INSTANCE + .nodeToNodeDto( root, new CycleContext(), new FactoryContext( 0, MAGIC_NUMBER_OFFSET ) ); + assertResult( rootDto ); + + NodeDto updated = new NodeDto( 0 ); + AutomappingNodeMapperWithContext.INSTANCE + .nodeToNodeDto( root, updated, new CycleContext(), new FactoryContext( 1, 10 ) ); + assertResult( updated ); + } + + @ProcessorTest + public void automappingWithSelfContainingContextCorrectlyResolvesCycles() { + Node root = buildNodes(); + NodeDto rootDto = AutomappingNodeMapperWithSelfContainingContext.INSTANCE + .nodeToNodeDto( + root, + new SelfContainingCycleContext(), + new FactoryContext( 0, MAGIC_NUMBER_OFFSET ) ); + assertResult( rootDto ); + + NodeDto updated = new NodeDto( 0 ); + AutomappingNodeMapperWithSelfContainingContext.INSTANCE + .nodeToNodeDto( + root, + updated, + new SelfContainingCycleContext(), + new FactoryContext( 1, 10 ) ); + assertResult( updated ); + } + + private void assertResult(NodeDto rootDto) { + assertThat( rootDto ).isNotNull(); + assertThat( rootDto.getId() ).isEqualTo( 0 ); + + AttributeDto rootAttribute = rootDto.getAttributes().get( 0 ); + assertThat( rootAttribute.getNode() ).isSameAs( rootDto ); + assertThat( rootAttribute.getMagicNumber() ).isEqualTo( 1 + MAGIC_NUMBER_OFFSET ); + + assertThat( rootDto.getChildren() ).hasSize( 1 ); + + NodeDto node1 = rootDto.getChildren().get( 0 ); + assertThat( node1.getParent() ).isSameAs( rootDto ); + assertThat( node1.getId() ).isEqualTo( 1 ); + + AttributeDto node1Attribute = node1.getAttributes().get( 0 ); + assertThat( node1Attribute.getNode() ).isSameAs( node1 ); + assertThat( node1Attribute.getMagicNumber() ).isEqualTo( 2 + MAGIC_NUMBER_OFFSET ); + + } + + private static Node buildNodes() { + Node root = new Node( "root" ); + root.addAttribute( new Attribute( "name", "root", 1 ) ); + + Node node1 = new Node( "node1" ); + node1.addAttribute( new Attribute( "name", "node1", 2 ) ); + + root.addChild( node1 ); + + return root; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/CycleContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContext.java new file mode 100644 index 0000000000..da6c87ecdf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContext.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.ap.test.context; + +import java.util.IdentityHashMap; +import java.util.Map; + +import org.mapstruct.Context; + +/** + * A type to be used as {@link Context} parameter to track cycles in graphs + * + * @author Andreas Gudian + */ +public class CycleContext { + private Map knownInstances = new IdentityHashMap<>(); + + @SuppressWarnings("unchecked") + public T getMappedInstance(Object source, Class targetType) { + return (T) knownInstances.get( source ); + } + + public void storeMappedInstance(Object source, Object target) { + knownInstances.put( source, target ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/CycleContextLifecycleMethods.java b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContextLifecycleMethods.java new file mode 100644 index 0000000000..edca0142d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContextLifecycleMethods.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.ap.test.context; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +/** + * @author Andreas Gudian + */ +public class CycleContextLifecycleMethods { + + @BeforeMapping + public T getInstance(Object source, @TargetType Class type, @Context CycleContext cycleContext) { + return cycleContext.getMappedInstance( source, type ); + } + + @BeforeMapping + public void setInstance(Object source, @MappingTarget Object target, @Context CycleContext cycleContext) { + cycleContext.storeMappedInstance( source, target ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContext.java new file mode 100644 index 0000000000..28194513bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContext.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.ap.test.context; + +import org.mapstruct.Context; +import org.mapstruct.ap.test.context.Node.Attribute; +import org.mapstruct.ap.test.context.NodeDto.AttributeDto; + +/** + * A type to be used as {@link Context} parameter to create NodeDto and AttributeDto instances + * + * @author Andreas Gudian + */ +public class FactoryContext { + private int nodeCounter; + private int attributeMagicNumberOffset; + + public FactoryContext(int initialCounter, int attributeMagicNumberOffset) { + this.nodeCounter = initialCounter; + this.attributeMagicNumberOffset = attributeMagicNumberOffset; + } + + public NodeDto createNode() { + return new NodeDto( nodeCounter++ ); + } + + public AttributeDto createAttributeDto(Attribute source) { + return new AttributeDto( source.getMagicNumber() + attributeMagicNumberOffset ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContextMethods.java b/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContextMethods.java new file mode 100644 index 0000000000..87908f1238 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/FactoryContextMethods.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.ap.test.context; + +import org.mapstruct.Context; +import org.mapstruct.ObjectFactory; +import org.mapstruct.ap.test.context.Node.Attribute; +import org.mapstruct.ap.test.context.NodeDto.AttributeDto; + +/** + * @author Andreas Gudian + */ +public class FactoryContextMethods { + + public NodeDto createNodeDto(@Context FactoryContext context) { + return context.createNode(); + } + + @ObjectFactory + public AttributeDto createAttributeDto(Attribute source, @Context FactoryContext context) { + return context.createAttributeDto( source ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/Node.java b/processor/src/test/java/org/mapstruct/ap/test/context/Node.java new file mode 100644 index 0000000000..b663b7b15f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/Node.java @@ -0,0 +1,87 @@ +/* + * 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.test.context; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Andreas Gudian + */ +public class Node { + private Node parent; + + private String name; + + private List children; + private List attributes; + + public Node(String name) { + this.name = name; + this.children = new ArrayList<>(); + this.attributes = new ArrayList<>(); + } + + public Node getParent() { + return parent; + } + + public String getName() { + return name; + } + + public List getChildren() { + return children; + } + + public void addChild(Node node) { + children.add( node ); + node.parent = this; + } + + public List getAttributes() { + return attributes; + } + + public void addAttribute(Attribute attribute) { + attributes.add( attribute ); + attribute.setNode( this ); + } + + public static class Attribute { + private Node node; + + private String name; + private String value; + private int magicNumber; + + public Attribute(String name, String value, int magicNumber) { + this.name = name; + this.value = value; + this.magicNumber = magicNumber; + } + + public Node getNode() { + return node; + } + + public void setNode(Node node) { + this.node = node; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public int getMagicNumber() { + return magicNumber; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/NodeDto.java b/processor/src/test/java/org/mapstruct/ap/test/context/NodeDto.java new file mode 100644 index 0000000000..0d117aa487 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/NodeDto.java @@ -0,0 +1,101 @@ +/* + * 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.test.context; + +import java.util.List; + +/** + * @author Andreas Gudian + */ +public class NodeDto { + private NodeDto parent; + + private int id; + private String name; + + private List children; + private List attributes; + + public NodeDto(int id) { + this.id = id; + } + + public NodeDto getParent() { + return parent; + } + + public void setParent(NodeDto parent) { + this.parent = parent; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + public static class AttributeDto { + private NodeDto node; + + private String name; + private String value; + private int magicNumber; + + public AttributeDto(int magicNumber) { + this.magicNumber = magicNumber; + } + + public NodeDto getNode() { + return node; + } + + public void setNode(NodeDto node) { + this.node = node; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public int getMagicNumber() { + return magicNumber; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/NodeMapperWithContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/NodeMapperWithContext.java new file mode 100644 index 0000000000..8ebdb5c64a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/NodeMapperWithContext.java @@ -0,0 +1,32 @@ +/* + * 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.test.context; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.ap.test.context.Node.Attribute; +import org.mapstruct.ap.test.context.NodeDto.AttributeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper(uses = { CycleContextLifecycleMethods.class, FactoryContextMethods.class }) +public interface NodeMapperWithContext { + NodeMapperWithContext INSTANCE = Mappers.getMapper( NodeMapperWithContext.class ); + + NodeDto nodeToNodeDto(@Context FactoryContext factoryContext, Node node, @Context CycleContext cycleContext); + + void nodeToNodeDto(@Context FactoryContext factoryContext, Node node, @MappingTarget NodeDto nodeDto, + @Context CycleContext cycleContext); + + AttributeDto attributeToAttributeDto(Attribute attribute, @Context CycleContext cycleContext, + @Context FactoryContext factoryContext); + + void attributeToAttributeDto(Attribute attribute, @MappingTarget AttributeDto nodeDto, + @Context CycleContext cycleContext, @Context FactoryContext factoryContext); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/SelfContainingCycleContext.java b/processor/src/test/java/org/mapstruct/ap/test/context/SelfContainingCycleContext.java new file mode 100644 index 0000000000..8b4a21640f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/SelfContainingCycleContext.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.test.context; + +import java.util.IdentityHashMap; +import java.util.Map; + +import org.mapstruct.BeforeMapping; +import org.mapstruct.Context; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +/** + * A type to be used as {@link Context} parameter to track cycles in graphs. + * + * @author Andreas Gudian + */ +public class SelfContainingCycleContext { + private Map knownInstances = new IdentityHashMap<>(); + + @BeforeMapping + @SuppressWarnings("unchecked") + public T getMappedInstance(Object source, @TargetType Class targetType) { + return (T) knownInstances.get( source ); + } + + @BeforeMapping + public void storeMappedInstance(Object source, @MappingTarget Object target) { + knownInstances.put( source, target ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/erroneous/ErroneousNodeMapperWithNonUniqueContextTypes.java b/processor/src/test/java/org/mapstruct/ap/test/context/erroneous/ErroneousNodeMapperWithNonUniqueContextTypes.java new file mode 100644 index 0000000000..210f7fc968 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/erroneous/ErroneousNodeMapperWithNonUniqueContextTypes.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.ap.test.context.erroneous; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.context.CycleContext; +import org.mapstruct.ap.test.context.Node; +import org.mapstruct.ap.test.context.NodeDto; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface ErroneousNodeMapperWithNonUniqueContextTypes { + + NodeDto nodeToNodeDto(Node node, @Context CycleContext cycleContext, @Context CycleContext otherCycleContext); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextObjectFactory.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextObjectFactory.java new file mode 100644 index 0000000000..af0e8b5145 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextObjectFactory.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.ap.test.context.objectfactory; + +import org.mapstruct.ObjectFactory; + +/** + * @author Andreas Gudian + */ +public class ContextObjectFactory { + + @ObjectFactory + public Valve create() { + return new Valve("123id"); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryMapper.java new file mode 100644 index 0000000000..b82d07f042 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryMapper.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.ap.test.context.objectfactory; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper +public interface ContextWithObjectFactoryMapper { + + ContextWithObjectFactoryMapper INSTANCE = Mappers.getMapper( ContextWithObjectFactoryMapper.class ); + + Valve map(ValveDto dto, @Context ContextObjectFactory factory); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryTest.java new file mode 100644 index 0000000000..068a5a0d95 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ContextWithObjectFactoryTest.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.test.context.objectfactory; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Sjaak Derksen + */ +@IssueKey( "1398" ) +@WithClasses({ + Valve.class, + ValveDto.class, + ContextObjectFactory.class, + ContextWithObjectFactoryMapper.class}) +public class ContextWithObjectFactoryTest { + + @ProcessorTest + public void testFactoryCalled( ) { + ValveDto dto = new ValveDto(); + dto.setOneWay( true ); + + Valve result = ContextWithObjectFactoryMapper.INSTANCE.map( dto, new ContextObjectFactory() ); + + assertThat( result ).isNotNull(); + assertThat( result.isOneWay() ).isTrue(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/Valve.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/Valve.java new file mode 100644 index 0000000000..887c4b3ec9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/Valve.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.ap.test.context.objectfactory; + +/** + * + * @author Sjaak Derksen + */ +public class Valve { + + private boolean oneWay; + private final String id; + + public Valve(String id) { + this.id = id; + } + + public boolean isOneWay() { + return oneWay; + } + + public void setOneWay(boolean oneWay) { + this.oneWay = oneWay; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ValveDto.java b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ValveDto.java new file mode 100644 index 0000000000..10c148f484 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/context/objectfactory/ValveDto.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.ap.test.context.objectfactory; + +/** + * + * @author Sjaak Derksen + */ +public class ValveDto { + + private boolean oneWay; + + public boolean isOneWay() { + return oneWay; + } + + public void setOneWay(boolean oneWay) { + this.oneWay = oneWay; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/ConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/ConversionTest.java index 5a86e8e9ce..d59f069458 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/ConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/ConversionTest.java @@ -1,35 +1,19 @@ -/** - * Copyright 2012-2015 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.conversion; -import static org.fest.assertions.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ConversionTest { - @Test + @ProcessorTest public void shouldApplyConversions() { Source source = new Source(); source.setFoo( 42 ); @@ -44,7 +28,7 @@ public void shouldApplyConversions() { assertThat( target.getZip() ).isEqualTo( "73" ); } - @Test + @ProcessorTest public void shouldHandleNulls() { Source source = new Source(); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -55,7 +39,7 @@ public void shouldHandleNulls() { assertThat( target.getZip() ).isEqualTo( "0" ); } - @Test + @ProcessorTest public void shouldApplyConversionsToMappedProperties() { Source source = new Source(); source.setQax( 42 ); @@ -68,7 +52,7 @@ public void shouldApplyConversionsToMappedProperties() { assertThat( target.getQax() ).isEqualTo( 23 ); } - @Test + @ProcessorTest public void shouldApplyConversionsForReverseMapping() { Target target = new Target(); target.setFoo( 42L ); @@ -83,7 +67,7 @@ public void shouldApplyConversionsForReverseMapping() { assertThat( source.getZip() ).isEqualTo( 73 ); } - @Test + @ProcessorTest public void shouldApplyConversionsToMappedPropertiesForReverseMapping() { Target target = new Target(); target.setQax( 42 ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/Source.java index 4c93dca9a8..78cb134897 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/SourceTargetMapper.java index e03c7b3c79..0bd3888809 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/SourceTargetMapper.java @@ -1,27 +1,14 @@ -/** - * Copyright 2012-2015 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.conversion; +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 @@ -30,10 +17,11 @@ public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mappings({ - @Mapping(source = "qax", target = "baz"), - @Mapping(source = "baz", target = "qax") + @Mapping(target = "baz", source = "qax"), + @Mapping(target = "qax", source = "baz") }) Target sourceToTarget(Source source); + @InheritInverseConfiguration Source targetToSource(Target target); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/Target.java index f133ab0c4f..8056ab6aa3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java new file mode 100644 index 0000000000..67d99dfbc6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerConversionTest.java @@ -0,0 +1,77 @@ +/* + * 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.test.conversion._enum; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests conversions between {@link Enum} and Integer. + * + * @author Jose Carlos Campanero Ortiz + */ +@IssueKey("2963") +@WithClasses({ + EnumToIntegerSource.class, + EnumToIntegerTarget.class, + EnumToIntegerMapper.class, + EnumToIntegerEnum.class +}) +public class EnumToIntegerConversionTest { + + @ProcessorTest + public void shouldApplyEnumToIntegerConversion() { + EnumToIntegerSource source = new EnumToIntegerSource(); + + for ( EnumToIntegerEnum value : EnumToIntegerEnum.values() ) { + source.setEnumValue( value ); + + EnumToIntegerTarget target = EnumToIntegerMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getEnumValue() ).isEqualTo( source.getEnumValue().ordinal() ); + } + } + + @ProcessorTest + public void shouldApplyReverseEnumToIntegerConversion() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + int numberOfValues = EnumToIntegerEnum.values().length; + for ( int value = 0; value < numberOfValues; value++ ) { + target.setEnumValue( value ); + + EnumToIntegerSource source = EnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).isEqualTo( EnumToIntegerEnum.values()[ target.getEnumValue() ] ); + } + } + + @ProcessorTest + public void shouldHandleOutOfBoundsEnumOrdinal() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + target.setInvalidEnumValue( EnumToIntegerEnum.values().length + 1 ); + + assertThatThrownBy( () -> EnumToIntegerMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( ArrayIndexOutOfBoundsException.class ); + } + + @ProcessorTest + public void shouldHandleNullIntegerValue() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + EnumToIntegerSource source = EnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).isNull(); + assertThat( source.getInvalidEnumValue() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerEnum.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerEnum.java new file mode 100644 index 0000000000..4aad7bef57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerEnum.java @@ -0,0 +1,14 @@ +/* + * 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.test.conversion._enum; + +public enum EnumToIntegerEnum { + ARBITRARY_VALUE_ZERO, + ARBITRARY_VALUE_ONE, + ARBITRARY_VALUE_TWO, + ARBITRARY_VALUE_THREE +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerMapper.java new file mode 100644 index 0000000000..946ee7c463 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerMapper.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.ap.test.conversion._enum; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface EnumToIntegerMapper { + EnumToIntegerMapper INSTANCE = Mappers.getMapper( EnumToIntegerMapper.class ); + + EnumToIntegerTarget sourceToTarget(EnumToIntegerSource source); + + EnumToIntegerSource targetToSource(EnumToIntegerTarget target); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerSource.java new file mode 100644 index 0000000000..4e60311d9b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerSource.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.ap.test.conversion._enum; + +public class EnumToIntegerSource { + private EnumToIntegerEnum enumValue; + + private EnumToIntegerEnum invalidEnumValue; + + public EnumToIntegerEnum getEnumValue() { + return enumValue; + } + + public void setEnumValue(final EnumToIntegerEnum enumValue) { + this.enumValue = enumValue; + } + + public EnumToIntegerEnum getInvalidEnumValue() { + return invalidEnumValue; + } + + public void setInvalidEnumValue(EnumToIntegerEnum invalidEnumValue) { + this.invalidEnumValue = invalidEnumValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerTarget.java new file mode 100644 index 0000000000..a8819c27ed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/EnumToIntegerTarget.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.ap.test.conversion._enum; + +public class EnumToIntegerTarget { + private Integer enumValue; + + private Integer invalidEnumValue; + + public Integer getEnumValue() { + return enumValue; + } + + public void setEnumValue(final Integer enumValue) { + this.enumValue = enumValue; + } + + public Integer getInvalidEnumValue() { + return invalidEnumValue; + } + + public void setInvalidEnumValue(Integer invalidEnumValue) { + this.invalidEnumValue = invalidEnumValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java new file mode 100644 index 0000000000..e1e557a897 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerConversionTest.java @@ -0,0 +1,73 @@ +/* + * 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.test.conversion._enum; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@WithClasses({ + OptionalEnumToIntegerSource.class, + EnumToIntegerTarget.class, + OptionalEnumToIntegerMapper.class, + EnumToIntegerEnum.class +}) +public class OptionalEnumToIntegerConversionTest { + + @ProcessorTest + public void shouldApplyEnumToIntegerConversion() { + OptionalEnumToIntegerSource source = new OptionalEnumToIntegerSource(); + + for ( EnumToIntegerEnum value : EnumToIntegerEnum.values() ) { + source.setEnumValue( Optional.of( value ) ); + + EnumToIntegerTarget target = OptionalEnumToIntegerMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getEnumValue() ).isEqualTo( value.ordinal() ); + } + } + + @ProcessorTest + public void shouldApplyReverseEnumToIntegerConversion() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + EnumToIntegerEnum[] enumValues = EnumToIntegerEnum.values(); + int numberOfValues = enumValues.length; + for ( int value = 0; value < numberOfValues; value++ ) { + target.setEnumValue( value ); + + OptionalEnumToIntegerSource source = OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).contains( enumValues[target.getEnumValue()] ); + } + } + + @ProcessorTest + public void shouldHandleOutOfBoundsEnumOrdinal() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + target.setInvalidEnumValue( EnumToIntegerEnum.values().length + 1 ); + + assertThatThrownBy( () -> OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( ArrayIndexOutOfBoundsException.class ); + } + + @ProcessorTest + public void shouldHandleNullIntegerValue() { + EnumToIntegerTarget target = new EnumToIntegerTarget(); + + OptionalEnumToIntegerSource source = OptionalEnumToIntegerMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getEnumValue() ).isEmpty(); + assertThat( source.getInvalidEnumValue() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.java new file mode 100644 index 0000000000..d07d9b879c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerMapper.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.ap.test.conversion._enum; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalEnumToIntegerMapper { + OptionalEnumToIntegerMapper INSTANCE = Mappers.getMapper( OptionalEnumToIntegerMapper.class ); + + EnumToIntegerTarget sourceToTarget(OptionalEnumToIntegerSource source); + + OptionalEnumToIntegerSource targetToSource(EnumToIntegerTarget target); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.java new file mode 100644 index 0000000000..700c3198bc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/_enum/OptionalEnumToIntegerSource.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.ap.test.conversion._enum; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalEnumToIntegerSource { + private Optional enumValue = Optional.empty(); + + private Optional invalidEnumValue = Optional.empty(); + + public Optional getEnumValue() { + return enumValue; + } + + public void setEnumValue(Optional enumValue) { + this.enumValue = enumValue; + } + + public Optional getInvalidEnumValue() { + return invalidEnumValue; + } + + public void setInvalidEnumValue(Optional invalidEnumValue) { + this.invalidEnumValue = invalidEnumValue; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalMapper.java index 58d04d89b7..30bd6ba9ad 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.bignumbers; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.java new file mode 100644 index 0000000000..986de6a0ed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalMapper.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.ap.test.conversion.bignumbers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BigDecimalOptionalMapper { + + BigDecimalOptionalMapper INSTANCE = Mappers.getMapper( BigDecimalOptionalMapper.class ); + + BigDecimalTarget sourceToTarget(BigDecimalOptionalSource source); + + BigDecimalOptionalSource targetToSource(BigDecimalTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java new file mode 100644 index 0000000000..bf4e53e5d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalOptionalSource.java @@ -0,0 +1,139 @@ +/* + * 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.test.conversion.bignumbers; + +import java.math.BigDecimal; +import java.util.Optional; + +public class BigDecimalOptionalSource { + + private Optional b; + private Optional bb; + private Optional s; + private Optional ss; + private Optional i; + private Optional ii; + private Optional l; + private Optional ll; + private Optional f; + private Optional ff; + private Optional d; + private Optional dd; + private Optional string; + private Optional bigInteger; + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } + + public Optional getString() { + return string; + } + + public void setString(Optional string) { + this.string = string; + } + + public Optional getBigInteger() { + return bigInteger; + } + + public void setBigInteger(Optional bigInteger) { + this.bigInteger = bigInteger; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalSource.java index 3272f40dd3..d52cb69f41 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.bignumbers; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalTarget.java index 0fbfba3dce..18843a25dd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigDecimalTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.bignumbers; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerMapper.java index b057df86cf..1663df8f51 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.bignumbers; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.java new file mode 100644 index 0000000000..d39b48c7e5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalMapper.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.ap.test.conversion.bignumbers; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BigIntegerOptionalMapper { + + BigIntegerOptionalMapper INSTANCE = Mappers.getMapper( BigIntegerOptionalMapper.class ); + + BigIntegerTarget sourceToTarget(BigIntegerOptionalSource source); + + BigIntegerOptionalSource targetToSource(BigIntegerTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java new file mode 100644 index 0000000000..f4bb0f03c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerOptionalSource.java @@ -0,0 +1,131 @@ +/* + * 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.test.conversion.bignumbers; + +import java.math.BigInteger; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class BigIntegerOptionalSource { + + private Optional b; + private Optional bb; + private Optional s; + private Optional ss; + private Optional i; + private Optional ii; + private Optional l; + private Optional ll; + private Optional f; + private Optional ff; + private Optional d; + private Optional dd; + private Optional string; + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } + + public Optional getString() { + return string; + } + + public void setString(Optional string) { + this.string = string; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerSource.java index 9939f4d3e8..34821583cd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.bignumbers; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerTarget.java index 82da595bbd..4204715d3b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigIntegerTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.bignumbers; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java index af37abe7d2..2ddddec02e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java @@ -1,43 +1,33 @@ -/** - * Copyright 2012-2015 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.conversion.bignumbers; -import static org.fest.assertions.Assertions.assertThat; - import java.math.BigDecimal; import java.math.BigInteger; +import java.util.Optional; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; /** * Tests conversions between {@link BigInteger} and numbers as well as String. * * @author Gunnar Morling */ -@RunWith(AnnotationProcessorTestRunner.class) public class BigNumbersConversionTest { - @Test + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest @IssueKey("21") @WithClasses({ BigIntegerSource.class, BigIntegerTarget.class, BigIntegerMapper.class }) public void shouldApplyBigIntegerConversions() { @@ -74,7 +64,7 @@ public void shouldApplyBigIntegerConversions() { assertThat( target.getString() ).isEqualTo( "13" ); } - @Test + @ProcessorTest @IssueKey("21") @WithClasses({ BigIntegerSource.class, BigIntegerTarget.class, BigIntegerMapper.class }) public void shouldApplyReverseBigIntegerConversions() { @@ -111,7 +101,80 @@ public void shouldApplyReverseBigIntegerConversions() { assertThat( source.getString() ).isEqualTo( new BigInteger( "13" ) ); } - @Test + @ProcessorTest + @WithClasses({ BigIntegerOptionalSource.class, BigIntegerTarget.class, BigIntegerOptionalMapper.class }) + public void shouldApplyOptionalBigIntegerConversions() { + BigIntegerOptionalSource source = new BigIntegerOptionalSource(); + source.setB( Optional.of( new BigInteger( "1" ) ) ); + source.setBb( Optional.of( new BigInteger( "2" ) ) ); + source.setS( Optional.of( new BigInteger( "3" ) ) ); + source.setSs( Optional.of( new BigInteger( "4" ) ) ); + source.setI( Optional.of( new BigInteger( "5" ) ) ); + source.setIi( Optional.of( new BigInteger( "6" ) ) ); + source.setL( Optional.of( new BigInteger( "7" ) ) ); + source.setLl( Optional.of( new BigInteger( "8" ) ) ); + source.setF( Optional.of( new BigInteger( "9" ) ) ); + source.setFf( Optional.of( new BigInteger( "10" ) ) ); + source.setD( Optional.of( new BigInteger( "11" ) ) ); + source.setDd( Optional.of( new BigInteger( "12" ) ) ); + source.setString( Optional.of( new BigInteger( "13" ) ) ); + + BigIntegerTarget target = BigIntegerOptionalMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( (byte) 2 ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( (short) 4 ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( 6 ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( 8 ); + assertThat( target.getF() ).isEqualTo( 9.0f ); + assertThat( target.getFf() ).isEqualTo( 10.0f ); + assertThat( target.getD() ).isEqualTo( 11.0d ); + assertThat( target.getDd() ).isEqualTo( 12.0d ); + assertThat( target.getString() ).isEqualTo( "13" ); + } + + @ProcessorTest + @IssueKey("21") + @WithClasses({ BigIntegerOptionalSource.class, BigIntegerTarget.class, BigIntegerOptionalMapper.class }) + public void shouldApplyReverseOptionalBigIntegerConversions() { + BigIntegerTarget target = new BigIntegerTarget(); + target.setB( (byte) 1 ); + target.setBb( (byte) 2 ); + target.setS( (short) 3 ); + target.setSs( (short) 4 ); + target.setI( 5 ); + target.setIi( 6 ); + target.setL( 7 ); + target.setLl( 8L ); + target.setF( 9.0f ); + target.setFf( 10.0f ); + target.setD( 11.0d ); + target.setDd( 12.0d ); + target.setString( "13" ); + + BigIntegerOptionalSource source = BigIntegerOptionalMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getB() ).contains( new BigInteger( "1" ) ); + assertThat( source.getBb() ).contains( new BigInteger( "2" ) ); + assertThat( source.getS() ).contains( new BigInteger( "3" ) ); + assertThat( source.getSs() ).contains( new BigInteger( "4" ) ); + assertThat( source.getI() ).contains( new BigInteger( "5" ) ); + assertThat( source.getIi() ).contains( new BigInteger( "6" ) ); + assertThat( source.getL() ).contains( new BigInteger( "7" ) ); + assertThat( source.getLl() ).contains( new BigInteger( "8" ) ); + assertThat( source.getF() ).contains( new BigInteger( "9" ) ); + assertThat( source.getFf() ).contains( new BigInteger( "10" ) ); + assertThat( source.getD() ).contains( new BigInteger( "11" ) ); + assertThat( source.getDd() ).contains( new BigInteger( "12" ) ); + assertThat( source.getString() ).contains( new BigInteger( "13" ) ); + } + + @ProcessorTest @IssueKey("21") @WithClasses({ BigDecimalSource.class, BigDecimalTarget.class, BigDecimalMapper.class }) public void shouldApplyBigDecimalConversions() { @@ -150,7 +213,7 @@ public void shouldApplyBigDecimalConversions() { assertThat( target.getBigInteger() ).isEqualTo( new BigInteger( "14" ) ); } - @Test + @ProcessorTest @IssueKey("21") @WithClasses({ BigDecimalSource.class, BigDecimalTarget.class, BigDecimalMapper.class }) public void shouldApplyReverseBigDecimalConversions() { @@ -188,4 +251,87 @@ public void shouldApplyReverseBigDecimalConversions() { assertThat( source.getString() ).isEqualTo( new BigDecimal( "13.45" ) ); assertThat( source.getBigInteger() ).isEqualTo( new BigDecimal( "14" ) ); } + + @ProcessorTest + @WithClasses({ BigDecimalOptionalSource.class, BigDecimalTarget.class, BigDecimalOptionalMapper.class }) + public void shouldApplyOptionalBigDecimalConversions() { + BigDecimalOptionalSource source = new BigDecimalOptionalSource(); + source.setB( Optional.of( new BigDecimal( "1.45" ) ) ); + source.setBb( Optional.of( new BigDecimal( "2.45" ) ) ); + source.setS( Optional.of( new BigDecimal( "3.45" ) ) ); + source.setSs( Optional.of( new BigDecimal( "4.45" ) ) ); + source.setI( Optional.of( new BigDecimal( "5.45" ) ) ); + source.setIi( Optional.of( new BigDecimal( "6.45" ) ) ); + source.setL( Optional.of( new BigDecimal( "7.45" ) ) ); + source.setLl( Optional.of( new BigDecimal( "8.45" ) ) ); + source.setF( Optional.of( new BigDecimal( "9.45" ) ) ); + source.setFf( Optional.of( new BigDecimal( "10.45" ) ) ); + source.setD( Optional.of( new BigDecimal( "11.45" ) ) ); + source.setDd( Optional.of( new BigDecimal( "12.45" ) ) ); + source.setString( Optional.of( new BigDecimal( "13.45" ) ) ); + source.setBigInteger( Optional.of( new BigDecimal( "14.45" ) ) ); + + BigDecimalTarget target = BigDecimalOptionalMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( (byte) 2 ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( (short) 4 ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( 6 ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( 8 ); + assertThat( target.getF() ).isEqualTo( 9.45f ); + assertThat( target.getFf() ).isEqualTo( 10.45f ); + assertThat( target.getD() ).isEqualTo( 11.45d ); + assertThat( target.getDd() ).isEqualTo( 12.45d ); + assertThat( target.getString() ).isEqualTo( "13.45" ); + assertThat( target.getBigInteger() ).isEqualTo( new BigInteger( "14" ) ); + } + + @ProcessorTest + @WithClasses({ BigDecimalOptionalSource.class, BigDecimalTarget.class, BigDecimalOptionalMapper.class }) + public void shouldApplyReverseOptionalBigDecimalConversions() { + BigDecimalTarget target = new BigDecimalTarget(); + target.setB( (byte) 1 ); + target.setBb( (byte) 2 ); + target.setS( (short) 3 ); + target.setSs( (short) 4 ); + target.setI( 5 ); + target.setIi( 6 ); + target.setL( 7 ); + target.setLl( 8L ); + target.setF( 9.0f ); + target.setFf( 10.0f ); + target.setD( 11.0d ); + target.setDd( 12.0d ); + target.setString( "13.45" ); + target.setBigInteger( new BigInteger( "14" ) ); + + BigDecimalOptionalSource source = BigDecimalOptionalMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getB() ).contains( new BigDecimal( "1" ) ); + assertThat( source.getBb() ).contains( new BigDecimal( "2" ) ); + assertThat( source.getS() ).contains( new BigDecimal( "3" ) ); + assertThat( source.getSs() ).contains( new BigDecimal( "4" ) ); + assertThat( source.getI() ).contains( new BigDecimal( "5" ) ); + assertThat( source.getIi() ).contains( new BigDecimal( "6" ) ); + assertThat( source.getL() ).contains( new BigDecimal( "7" ) ); + assertThat( source.getLl() ).contains( new BigDecimal( "8" ) ); + assertThat( source.getF() ).contains( new BigDecimal( "9.0" ) ); + assertThat( source.getFf() ).contains( new BigDecimal( "10.0" ) ); + assertThat( source.getD() ).contains( new BigDecimal( "11.0" ) ); + assertThat( source.getDd() ).contains( new BigDecimal( "12.0" ) ); + assertThat( source.getString() ).contains( new BigDecimal( "13.45" ) ); + assertThat( source.getBigInteger() ).contains( new BigDecimal( "14" ) ); + } + + @ProcessorTest + @IssueKey("1009") + @WithClasses({ BigIntegerSource.class, BigIntegerTarget.class, BigIntegerMapper.class }) + public void shouldNotGenerateCreateDecimalFormatMethod() { + generatedSource.forMapper( BigIntegerMapper.class ).content().doesNotContain( "createDecimalFormat" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyConversionTest.java new file mode 100644 index 0000000000..57b4f7e4e4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyConversionTest.java @@ -0,0 +1,57 @@ +/* + * 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.test.conversion.currency; + +import java.util.Currency; +import java.util.HashSet; +import java.util.Set; + +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Darren Rambaud + */ +@WithClasses({ CurrencyMapper.class, CurrencySource.class, CurrencyTarget.class }) +@IssueKey("1355") +public class CurrencyConversionTest { + + @ProcessorTest + public void shouldApplyCurrencyConversions() { + final CurrencySource source = new CurrencySource(); + source.setCurrencyA( Currency.getInstance( "USD" ) ); + Set currencies = new HashSet<>(); + currencies.add( Currency.getInstance( "EUR" ) ); + currencies.add( Currency.getInstance( "CHF" ) ); + source.setUniqueCurrencies( currencies ); + + CurrencyTarget target = CurrencyMapper.INSTANCE.currencySourceToCurrencyTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getCurrencyA() ).isEqualTo( "USD" ); + assertThat( target.getUniqueCurrencies() ) + .isNotEmpty() + .containsExactlyInAnyOrder( "EUR", "CHF" ); + } + + @ProcessorTest + public void shouldApplyReverseConversions() { + final CurrencyTarget target = new CurrencyTarget(); + target.setCurrencyA( "USD" ); + target.setUniqueCurrencies( Collections.asSet( "JPY" ) ); + + CurrencySource source = CurrencyMapper.INSTANCE.currencyTargetToCurrencySource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getCurrencyA().getCurrencyCode() ).isEqualTo( Currency.getInstance( "USD" ) + .getCurrencyCode() ); + assertThat( source.getUniqueCurrencies() ).containsExactlyInAnyOrder( Currency.getInstance( "JPY" ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyMapper.java new file mode 100644 index 0000000000..eae0e41bcd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyMapper.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.ap.test.conversion.currency; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Darren Rambaud + */ +@Mapper +public interface CurrencyMapper { + CurrencyMapper INSTANCE = Mappers.getMapper( CurrencyMapper.class ); + + CurrencyTarget currencySourceToCurrencyTarget(CurrencySource source); + + CurrencySource currencyTargetToCurrencySource(CurrencyTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencySource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencySource.java new file mode 100644 index 0000000000..a7a9408b98 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencySource.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.test.conversion.currency; + +import java.util.Currency; +import java.util.Set; + +/** + * @author Darren Rambaud + */ +public class CurrencySource { + + private Currency currencyA; + private Set uniqueCurrencies; + + public Currency getCurrencyA() { + return this.currencyA; + } + + public void setCurrencyA(final Currency currencyA) { + this.currencyA = currencyA; + } + + public Set getUniqueCurrencies() { + return this.uniqueCurrencies; + } + + public void setUniqueCurrencies(final Set uniqueCurrencies) { + this.uniqueCurrencies = uniqueCurrencies; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyTarget.java new file mode 100644 index 0000000000..384decb106 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/currency/CurrencyTarget.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.ap.test.conversion.currency; + +import java.util.Set; + +/** + * @author Darren Rambaud + */ +public class CurrencyTarget { + + private String currencyA; + private Set uniqueCurrencies; + + public String getCurrencyA() { + return this.currencyA; + } + + public void setCurrencyA(final String currencyA) { + this.currencyA = currencyA; + } + + public Set getUniqueCurrencies() { + return this.uniqueCurrencies; + } + + public void setUniqueCurrencies(final Set uniqueCurrencies) { + this.uniqueCurrencies = uniqueCurrencies; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java index 78f158ccff..346f34a054 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/DateConversionTest.java @@ -1,37 +1,25 @@ -/** - * Copyright 2012-2015 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.test.conversion.date; -import static org.fest.assertions.Assertions.assertThat; - +import java.sql.Time; +import java.sql.Timestamp; import java.util.Arrays; +import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import java.util.List; -import java.util.Locale; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junitpioneer.jupiter.DefaultLocale; +import org.junitpioneer.jupiter.ReadsDefaultTimeZone; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * Tests application of format strings for conversions between strings and dates. @@ -44,46 +32,72 @@ SourceTargetMapper.class }) @IssueKey("43") -@RunWith(AnnotationProcessorTestRunner.class) +@DefaultLocale("de") +@ReadsDefaultTimeZone public class DateConversionTest { - @Before - public void setDefaultLocale() { - Locale.setDefault( Locale.GERMAN ); - } - - @Test - public void shouldApplyDateFormatForConversions() { + @ProcessorTest + public void shouldApplyDateFormatForConversionsJdk11() { Source source = new Source(); - source.setDate( new GregorianCalendar( 2013, 6, 6 ).getTime() ); - source.setAnotherDate( new GregorianCalendar( 2013, 1, 14 ).getTime() ); + source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getDate() ).isEqualTo( "06.07.2013" ); - assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13 00:00" ); + assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); + } + + @ProcessorTest + public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { + Source source = new Source(); + source.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + source.setAnotherDate( new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime() ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); + assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); } - @Test - public void shouldApplyDateFormatForConversionInReverseMapping() { + @ProcessorTest + public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { Target target = new Target(); target.setDate( "06.07.2013" ); - target.setAnotherDate( "14.02.13 8:30" ); + target.setAnotherDate( "14.02.13, 8:30" ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( source ).isNotNull(); - assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, 6, 6 ).getTime() ); - assertThat( source.getAnotherDate() ).isEqualTo( new GregorianCalendar( 2013, 1, 14, 8, 30 ).getTime() ); + assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + assertThat( source.getAnotherDate() ).isEqualTo( + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() + ); + } + + @ProcessorTest + public void shouldApplyDateFormatForConversionInReverseMappingJdk11WithCustomLocale() { + Target target = new Target(); + target.setDate( "juillet 06, 2013" ); + target.setAnotherDate( "14.02.13, 8:30" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); + assertThat( source.getAnotherDate() ).isEqualTo( + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14, 8, 30 ).getTime() + ); } - @Test + @ProcessorTest public void shouldApplyStringConversionForIterableMethod() { List dates = Arrays.asList( - new GregorianCalendar( 2013, 6, 6 ).getTime(), - new GregorianCalendar( 2013, 1, 14 ).getTime(), - new GregorianCalendar( 2013, 3, 11 ).getTime() + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() ); List stringDates = SourceTargetMapper.INSTANCE.stringListToDateList( dates ); @@ -92,12 +106,26 @@ public void shouldApplyStringConversionForIterableMethod() { assertThat( stringDates ).containsExactly( "06.07.2013", "14.02.2013", "11.04.2013" ); } - @Test + @ProcessorTest + public void shouldApplyStringConversionForIterableMethodWithCustomLocale() { + List dates = Arrays.asList( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + + List stringDates = SourceTargetMapper.INSTANCE.stringListToDateListWithCustomLocale( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).containsExactly( "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" ); + } + + @ProcessorTest public void shouldApplyStringConversionForArrayMethod() { List dates = Arrays.asList( - new GregorianCalendar( 2013, 6, 6 ).getTime(), - new GregorianCalendar( 2013, 1, 14 ).getTime(), - new GregorianCalendar( 2013, 3, 11 ).getTime() + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() ); String[] stringDates = SourceTargetMapper.INSTANCE.stringListToDateArray( dates ); @@ -106,7 +134,21 @@ public void shouldApplyStringConversionForArrayMethod() { assertThat( stringDates ).isEqualTo( new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" } ); } - @Test + @ProcessorTest + public void shouldApplyStringConversionForArrayMethodWithCustomLocale() { + List dates = Arrays.asList( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + + String[] stringDates = SourceTargetMapper.INSTANCE.stringListToDateArrayWithCustomLocale( dates ); + + assertThat( stringDates ).isNotNull(); + assertThat( stringDates ).isEqualTo( new String[]{ "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" } ); + } + + @ProcessorTest public void shouldApplyStringConversionForReverseIterableMethod() { List stringDates = Arrays.asList( "06.07.2013", "14.02.2013", "11.04.2013" ); @@ -114,13 +156,27 @@ public void shouldApplyStringConversionForReverseIterableMethod() { assertThat( dates ).isNotNull(); assertThat( dates ).containsExactly( - new GregorianCalendar( 2013, 6, 6 ).getTime(), - new GregorianCalendar( 2013, 1, 14 ).getTime(), - new GregorianCalendar( 2013, 3, 11 ).getTime() + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + } + + @ProcessorTest + public void shouldApplyStringConversionForReverseIterableMethodWithCustomLocale() { + List stringDates = Arrays.asList( "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" ); + + List dates = SourceTargetMapper.INSTANCE.dateListToStringListWithCustomLocale( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).containsExactly( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() ); } - @Test + @ProcessorTest public void shouldApplyStringConversionForReverseArrayMethod() { String[] stringDates = new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" }; @@ -128,19 +184,32 @@ public void shouldApplyStringConversionForReverseArrayMethod() { assertThat( dates ).isNotNull(); assertThat( dates ).containsExactly( - new GregorianCalendar( 2013, 6, 6 ).getTime(), - new GregorianCalendar( 2013, 1, 14 ).getTime(), - new GregorianCalendar( 2013, 3, 11 ).getTime() + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() ); } + @ProcessorTest + public void shouldApplyStringConversionForReverseArrayMethodWithCustomLocale() { + String[] stringDates = new String[]{ "juillet 06, 2013", "février 14, 2013", "avril 11, 2013" }; + + List dates = SourceTargetMapper.INSTANCE.stringArrayToDateListWithCustomLocale( stringDates ); + + assertThat( dates ).isNotNull(); + assertThat( dates ).containsExactly( + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() + ); + } - @Test + @ProcessorTest public void shouldApplyStringConversionForReverseArrayArrayMethod() { Date[] dates = new Date[]{ - new GregorianCalendar( 2013, 6, 6 ).getTime(), - new GregorianCalendar( 2013, 1, 14 ).getTime(), - new GregorianCalendar( 2013, 3, 11 ).getTime() + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() }; String[] stringDates = SourceTargetMapper.INSTANCE.dateArrayToStringArray( dates ); @@ -148,7 +217,7 @@ public void shouldApplyStringConversionForReverseArrayArrayMethod() { assertThat( stringDates ).isEqualTo( new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" } ); } - @Test + @ProcessorTest public void shouldApplyDateConversionForReverseArrayArrayMethod() { String[] stringDates = new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" }; @@ -156,9 +225,51 @@ public void shouldApplyDateConversionForReverseArrayArrayMethod() { assertThat( dates ).isNotNull(); assertThat( dates ).isEqualTo( new Date[] { - new GregorianCalendar( 2013, 6, 6 ).getTime(), - new GregorianCalendar( 2013, 1, 14 ).getTime(), - new GregorianCalendar( 2013, 3, 11 ).getTime() + new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime(), + new GregorianCalendar( 2013, Calendar.FEBRUARY, 14 ).getTime(), + new GregorianCalendar( 2013, Calendar.APRIL, 11 ).getTime() } ); } + + @IssueKey("858") + @ProcessorTest + public void shouldApplyDateToSqlConversion() { + GregorianCalendar time = new GregorianCalendar( 2016, Calendar.AUGUST, 24, 20, 30, 30 ); + GregorianCalendar sqlDate = new GregorianCalendar( 2016, Calendar.AUGUST, 23, 21, 35, 35 ); + GregorianCalendar timestamp = new GregorianCalendar( 2016, Calendar.AUGUST, 22, 21, 35, 35 ); + Source source = new Source(); + source.setTime( time.getTime() ); + source.setSqlDate( sqlDate.getTime() ); + source.setTimestamp( timestamp.getTime() ); + + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + Time expectedTime = new Time( time.getTime().getTime() ); + java.sql.Date expectedSqlDate = new java.sql.Date( sqlDate.getTime().getTime() ); + Timestamp expectedTimestamp = new Timestamp( timestamp.getTime().getTime() ); + + assertThat( target.getTime() ).isEqualTo( expectedTime ); + assertThat( target.getSqlDate() ).isEqualTo( expectedSqlDate ); + assertThat( target.getTimestamp() ).isEqualTo( expectedTimestamp ); + } + + @IssueKey("858") + @ProcessorTest + public void shouldApplySqlToDateConversion() { + Target target = new Target(); + GregorianCalendar time = new GregorianCalendar( 2016, Calendar.AUGUST, 24, 20, 30, 30 ); + GregorianCalendar sqlDate = new GregorianCalendar( 2016, Calendar.AUGUST, 23, 21, 35, 35 ); + GregorianCalendar timestamp = new GregorianCalendar( 2016, Calendar.AUGUST, 22, 21, 35, 35 ); + target.setTime( new Time( time.getTime().getTime() ) ); + target.setSqlDate( new java.sql.Date( sqlDate.getTime().getTime() ) ); + target.setTimestamp( new Timestamp( timestamp.getTime().getTime() ) ); + + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getTime() ).isEqualTo( target.getTime() ); + assertThat( source.getSqlDate() ).isEqualTo( target.getSqlDate() ); + assertThat( source.getTimestamp() ).isEqualTo( target.getTimestamp() ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/Source.java index 0f66431214..619ddfb117 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.date; @@ -24,6 +11,9 @@ public class Source { private Date date; private Date anotherDate; + private Date time; + private Date sqlDate; + private Date timestamp; public Date getDate() { return date; @@ -40,4 +30,29 @@ public Date getAnotherDate() { public void setAnotherDate(Date anotherDate) { this.anotherDate = anotherDate; } + + public Date getTime() { + return time; + } + + public void setTime(Date time) { + this.time = time; + } + + public Date getSqlDate() { + return sqlDate; + } + + public void setSqlDate(Date sqlDate) { + this.sqlDate = sqlDate; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java index f29df6fc49..1971a8c066 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.date; @@ -35,21 +22,39 @@ public interface SourceTargetMapper { @Mapping(target = "date", dateFormat = "dd.MM.yyyy") Target sourceToTarget(Source source); - @InheritInverseConfiguration + @Mapping(target = "date", dateFormat = "MMMM dd, yyyy", locale = "fr") + Target sourceToTargetWithCustomLocale(Source source); + + @InheritInverseConfiguration(name = "sourceToTarget") Source targetToSource(Target target); + @InheritInverseConfiguration(name = "sourceToTargetWithCustomLocale") + Source targetToSourceWithCustomLocale(Target target); + @IterableMapping(dateFormat = "dd.MM.yyyy") List stringListToDateList(List dates); + @IterableMapping(dateFormat = "MMMM dd, yyyy", locale = "fr") + List stringListToDateListWithCustomLocale(List dates); + @IterableMapping(dateFormat = "dd.MM.yyyy") String[] stringListToDateArray(List dates); - @InheritInverseConfiguration + @IterableMapping(dateFormat = "MMMM dd, yyyy", locale = "fr") + String[] stringListToDateArrayWithCustomLocale(List dates); + + @InheritInverseConfiguration(name = "stringListToDateList") List dateListToStringList(List strings); - @InheritInverseConfiguration + @InheritInverseConfiguration(name = "stringListToDateListWithCustomLocale") + List dateListToStringListWithCustomLocale(List strings); + + @InheritInverseConfiguration(name = "stringListToDateArray") List stringArrayToDateList(String[] dates); + @InheritInverseConfiguration(name = "stringListToDateArrayWithCustomLocale") + List stringArrayToDateListWithCustomLocale(String[] dates); + @IterableMapping(dateFormat = "dd.MM.yyyy") String[] dateArrayToStringArray(Date[] dates); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/Target.java index 7608b647c9..745c599bd0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/date/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/date/Target.java @@ -1,27 +1,21 @@ -/** - * Copyright 2012-2015 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.conversion.date; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; + public class Target { private String date; private String anotherDate; + private Time time; + private Date sqlDate; + private Timestamp timestamp; public String getDate() { return date; @@ -38,4 +32,28 @@ public String getAnotherDate() { public void setAnotherDate(String anotherDate) { this.anotherDate = anotherDate; } + + public Time getTime() { + return time; + } + + public void setTime(Time time) { + this.time = time; + } + + public Date getSqlDate() { + return sqlDate; + } + + public void setSqlDate(Date sqlDate) { + this.sqlDate = sqlDate; + } + + public Timestamp getTimestamp() { + return timestamp; + } + + public void setTimestamp(Timestamp timestamp) { + this.timestamp = timestamp; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/ErroneousFormatMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/ErroneousFormatMapper.java new file mode 100644 index 0000000000..bee6db6345 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/ErroneousFormatMapper.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.ap.test.conversion.erroneous; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousFormatMapper { + + @Mappings( { + @Mapping(target = "date", dateFormat = "qwertz"), + @Mapping(target = "zonedDateTime", dateFormat = "qwertz"), + @Mapping(target = "localDateTime", dateFormat = "qwertz"), + @Mapping(target = "localDate", dateFormat = "qwertz"), + @Mapping(target = "localTime", dateFormat = "qwertz"), + @Mapping(target = "dateTime", dateFormat = "qwertz"), + @Mapping(target = "jodaLocalDateTime", dateFormat = "qwertz"), + @Mapping(target = "jodaLocalDate", dateFormat = "qwertz"), + @Mapping(target = "jodaLocalTime", dateFormat = "qwertz") + } ) + Target sourceToTarget(Source source); + + @IterableMapping(dateFormat = "qwertz") + List fromDates(List dates); + + @MapMapping(keyDateFormat = "qwertz") + Map fromDateKeys(Map dates); + + @MapMapping(valueDateFormat = "qwertz") + Map fromDateValues(Map dates); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/InvalidDateFormatTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/InvalidDateFormatTest.java new file mode 100644 index 0000000000..81bf3eb568 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/InvalidDateFormatTest.java @@ -0,0 +1,84 @@ +/* + * 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.test.conversion.erroneous; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJoda; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + Source.class, + Target.class +}) +@WithJoda +@IssueKey("725") +public class InvalidDateFormatTest { + + @WithClasses({ + ErroneousFormatMapper.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 25, + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 26, + message = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 27, + message = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 28, + message = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 29, + message = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 30, + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 31, + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 32, + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 33, + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 37, + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 40, + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\"."), + @Diagnostic(type = ErroneousFormatMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 43, + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\".") + }) + @ProcessorTest + public void shouldFailWithInvalidDateFormats() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/Source.java new file mode 100644 index 0000000000..4018fc1258 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/Source.java @@ -0,0 +1,104 @@ +/* + * 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.test.conversion.erroneous; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZonedDateTime; +import java.util.Date; + +import org.joda.time.DateTime; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private Date date; + + private ZonedDateTime zonedDateTime; + private LocalDateTime localDateTime; + private LocalDate localDate; + private LocalTime localTime; + + private DateTime dateTime; + private org.joda.time.LocalDateTime jodaLocalDateTime; + private org.joda.time.LocalDate jodaLocalDate; + private org.joda.time.LocalTime jodaLocalTime; + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } + + public LocalTime getLocalTime() { + return localTime; + } + + public void setLocalTime(LocalTime localTime) { + this.localTime = localTime; + } + + public DateTime getDateTime() { + return dateTime; + } + + public void setDateTime(DateTime dateTime) { + this.dateTime = dateTime; + } + + public org.joda.time.LocalDateTime getJodaLocalDateTime() { + return jodaLocalDateTime; + } + + public void setJodaLocalDateTime(org.joda.time.LocalDateTime jodaLocalDateTime) { + this.jodaLocalDateTime = jodaLocalDateTime; + } + + public org.joda.time.LocalDate getJodaLocalDate() { + return jodaLocalDate; + } + + public void setJodaLocalDate(org.joda.time.LocalDate jodaLocalDate) { + this.jodaLocalDate = jodaLocalDate; + } + + public org.joda.time.LocalTime getJodaLocalTime() { + return jodaLocalTime; + } + + public void setJodaLocalTime(org.joda.time.LocalTime jodaLocalTime) { + this.jodaLocalTime = jodaLocalTime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/Target.java new file mode 100644 index 0000000000..abaf46db7b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/Target.java @@ -0,0 +1,96 @@ +/* + * 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.test.conversion.erroneous; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String date; + + private String zonedDateTime; + private String localDateTime; + private String localDate; + private String localTime; + + private String dateTime; + private String jodaLocalDateTime; + private String jodaLocalDate; + private String jodaLocalTime; + + public String getDate() { + return date; + } + + public void setDate(String date) { + this.date = date; + } + + public String getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(String zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public String getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(String localDateTime) { + this.localDateTime = localDateTime; + } + + public String getLocalDate() { + return localDate; + } + + public void setLocalDate(String localDate) { + this.localDate = localDate; + } + + public String getLocalTime() { + return localTime; + } + + public void setLocalTime(String localTime) { + this.localTime = localTime; + } + + public String getDateTime() { + return dateTime; + } + + public void setDateTime(String dateTime) { + this.dateTime = dateTime; + } + + public String getJodaLocalDateTime() { + return jodaLocalDateTime; + } + + public void setJodaLocalDateTime(String jodaLocalDateTime) { + this.jodaLocalDateTime = jodaLocalDateTime; + } + + public String getJodaLocalDate() { + return jodaLocalDate; + } + + public void setJodaLocalDate(String jodaLocalDate) { + this.jodaLocalDate = jodaLocalDate; + } + + public String getJodaLocalTime() { + return jodaLocalTime; + } + + public void setJodaLocalTime(String jodaLocalTime) { + this.jodaLocalTime = jodaLocalTime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8CustomPatternDateTimeFormatterGeneratedTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8CustomPatternDateTimeFormatterGeneratedTest.java new file mode 100644 index 0000000000..443de8aab5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8CustomPatternDateTimeFormatterGeneratedTest.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.test.conversion.java8time; + +import java.time.LocalDateTime; +import java.time.Month; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conversion.java8time.custompatterndatetimeformattergenerated.Source; +import org.mapstruct.ap.test.conversion.java8time.custompatterndatetimeformattergenerated.SourceTargetMapper; +import org.mapstruct.ap.test.conversion.java8time.custompatterndatetimeformattergenerated.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests generation of DateTimeFormatters as mapper instance fields for conversions to/from Java 8 date and time types. + */ +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) +@IssueKey("2329") +public class Java8CustomPatternDateTimeFormatterGeneratedTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( SourceTargetMapper.class ); + + @ProcessorTest + public void testDateTimeFormattersGenerated() { + + Source source = new Source(); + source.setLocalDateTime1( LocalDateTime.of( 2021, Month.MAY, 16, 12, 13, 10 ) ); + source.setLocalDateTime2( LocalDateTime.of( 2020, Month.APRIL, 10, 15, 10, 12 ) ); + source.setLocalDateTime3( LocalDateTime.of( 2021, Month.APRIL, 25, 9, 46, 13 ) ); + + Target target = SourceTargetMapper.INSTANCE.map( source ); + + assertThat( target.getLocalDateTime1() ).isEqualTo( "16.05.2021 12:13" ); + assertThat( target.getLocalDateTime2() ).isEqualTo( "10.04.2020 15:10" ); + assertThat( target.getLocalDateTime3() ).isEqualTo( "25.04.2021 09.46" ); + + source = SourceTargetMapper.INSTANCE.map( target ); + + assertThat( source.getLocalDateTime1() ).isEqualTo( LocalDateTime.of( 2021, Month.MAY, 16, 12, 13, 0 ) ); + assertThat( source.getLocalDateTime2() ).isEqualTo( LocalDateTime.of( 2020, Month.APRIL, 10, 15, 10, 0 ) ); + assertThat( source.getLocalDateTime3() ).isEqualTo( LocalDateTime.of( 2021, Month.APRIL, 25, 9, 46, 0 ) ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java new file mode 100644 index 0000000000..1a9bb50b67 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8OptionalTimeConversionTest.java @@ -0,0 +1,466 @@ +/* + * 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.test.conversion.java8time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; +import java.util.Optional; +import java.util.TimeZone; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for conversions to/from Java 8 date and time types wrapped in {@link Optional}. + */ +@WithClasses({ OptionalSource.class, Target.class, OptionalSourceTargetMapper.class }) +public class Java8OptionalTimeConversionTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalSourceTargetMapper.class ); + } + + @ProcessorTest + public void testDateTimeToString() { + OptionalSource src = new OptionalSource(); + src.setZonedDateTime( Optional.of( ZonedDateTime.of( + LocalDateTime.of( 2014, 1, 1, 0, 0 ), + ZoneId.of( "UTC" ) + ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDateTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + } + + @ProcessorTest + public void testLocalDateTimeToString() { + OptionalSource src = new OptionalSource(); + src.setLocalDateTime( Optional.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalDateTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + } + + @ProcessorTest + public void testLocalDateToString() { + OptionalSource src = new OptionalSource(); + src.setLocalDate( Optional.of( LocalDate.of( 2014, 1, 1 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalDateMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + } + + @ProcessorTest + public void testLocalTimeToString() { + OptionalSource src = new OptionalSource(); + src.setLocalTime( Optional.ofNullable( LocalTime.of( 0, 0 ) ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetLocalTimeMapped( src ); + assertThat( target ).isNotNull(); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + } + + @ProcessorTest + public void testSourceToTargetMappingForStrings() { + OptionalSource src = new OptionalSource(); + src.setLocalTime( Optional.ofNullable( LocalTime.of( 0, 0 ) ) ); + src.setLocalDate( Optional.of( LocalDate.of( 2014, 1, 1 ) ) ); + src.setLocalDateTime( Optional.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ) ); + src.setZonedDateTime( Optional.of( ZonedDateTime.of( + LocalDateTime.of( 2014, 1, 1, 0, 0 ), + ZoneId.of( "UTC" ) + ) ) ); + + // with given format + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( src ); + + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + + // and now with default mappings + target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); + assertThat( target ).isNotNull(); + assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); + assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); + assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); + } + + @ProcessorTest + public void testStringToDateTime() { + String dateTimeAsString = "01.01.2014 00:00 UTC"; + Target target = new Target(); + target.setZonedDateTime( dateTimeAsString ); + ZonedDateTime sourceDateTime = + ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceDateTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getZonedDateTime() ).contains( sourceDateTime ); + } + + @ProcessorTest + public void testStringToLocalDateTime() { + String dateTimeAsString = "01.01.2014 00:00"; + Target target = new Target(); + target.setLocalDateTime( dateTimeAsString ); + LocalDateTime sourceDateTime = + LocalDateTime.of( 2014, 1, 1, 0, 0, 0 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalDateTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalDateTime() ).contains( sourceDateTime ); + } + + @ProcessorTest + public void testStringToLocalDate() { + String dateTimeAsString = "01.01.2014"; + Target target = new Target(); + target.setLocalDate( dateTimeAsString ); + LocalDate sourceDate = + LocalDate.of( 2014, 1, 1 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalDateMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalDate() ).contains( sourceDate ); + } + + @ProcessorTest + public void testStringToLocalTime() { + String dateTimeAsString = "00:00"; + Target target = new Target(); + target.setLocalTime( dateTimeAsString ); + LocalTime sourceTime = + LocalTime.of( 0, 0 ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSourceLocalTimeMapped( target ); + assertThat( src ).isNotNull(); + assertThat( src.getLocalTime() ).contains( sourceTime ); + } + + @ProcessorTest + public void testTargetToSourceNullMapping() { + Target target = new Target(); + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( src ).isNotNull(); + assertThat( src.getZonedDateTime() ).isEmpty(); + assertThat( src.getLocalDate() ).isEmpty(); + assertThat( src.getLocalDateTime() ).isEmpty(); + assertThat( src.getLocalTime() ).isEmpty(); + } + + @ProcessorTest + public void testTargetToSourceMappingForStrings() { + Target target = new Target(); + + target.setZonedDateTime( "01.01.2014 00:00 UTC" ); + target.setLocalDateTime( "01.01.2014 00:00" ); + target.setLocalDate( "01.01.2014" ); + target.setLocalTime( "00:00" ); + + OptionalSource src = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( src.getZonedDateTime() ).contains( + ZonedDateTime.of( + LocalDateTime.of( + 2014, + 1, + 1, + 0, + 0 + ), ZoneId.of( "UTC" ) + ) ); + assertThat( src.getLocalDateTime() ).contains( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + assertThat( src.getLocalDate() ).contains( LocalDate.of( 2014, 1, 1 ) ); + assertThat( src.getLocalTime() ).contains( LocalTime.of( 0, 0 ) ); + } + + @ProcessorTest + public void testCalendarMapping() { + OptionalSource source = new OptionalSource(); + ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + source.setForCalendarConversion( Optional.of( dateTime ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target.getForCalendarConversion() ).isNotNull(); + assertThat( target.getForCalendarConversion().getTimeZone() ).isEqualTo( + TimeZone.getTimeZone( + "UTC" ) ); + assertThat( target.getForCalendarConversion().get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( target.getForCalendarConversion().get( Calendar.MONTH ) ).isEqualTo( + dateTime.getMonthValue() - 1 ); + assertThat( target.getForCalendarConversion().get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( target.getForCalendarConversion().get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( target.getForCalendarConversion().get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForCalendarConversion() ).contains( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("UTC") + public void testZonedDateTimeToDateMapping() { + OptionalSource source = new OptionalSource(); + ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + source.setForDateConversionWithZonedDateTime( Optional.of( dateTime ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithZonedDateTime() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithZonedDateTime().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( dateTime.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( instance.get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( instance.get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithZonedDateTime() ).contains( dateTime ); + } + + @ProcessorTest + public void testInstantToDateMapping() { + Instant instant = Instant.ofEpochMilli( 1539366615000L ); + + OptionalSource source = new OptionalSource(); + source.setForDateConversionWithInstant( Optional.ofNullable( instant ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + Date date = target.getForDateConversionWithInstant(); + assertThat( date ).isNotNull(); + assertThat( date.getTime() ).isEqualTo( 1539366615000L ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDateConversionWithInstant() ).contains( instant ); + } + + @ProcessorTest + public void testLocalDateTimeToLocalDateMapping() { + LocalDate localDate = LocalDate.of( 2014, 1, 1 ); + + OptionalSource source = new OptionalSource(); + source.setForLocalDateTimeConversionWithLocalDate( Optional.of( localDate ) ); + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + LocalDateTime localDateTime = target.getForLocalDateTimeConversionWithLocalDate(); + assertThat( localDateTime ).isNotNull(); + assertThat( localDateTime ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForLocalDateTimeConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateTimeToDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDateTime dateTime = LocalDateTime.of( 2014, 1, 1, 0, 0 ); + source.setForDateConversionWithLocalDateTime( Optional.of( dateTime ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithLocalDateTime() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDateTime().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( dateTime.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); + assertThat( instance.get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); + assertThat( instance.get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDateTime() ).contains( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForDateConversionWithLocalDate( Optional.of( localDate ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToSqlDateMapping() { + + OptionalSource source = new OptionalSource(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForSqlDateConversionWithLocalDate( Optional.of( localDate ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForSqlDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForSqlDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForSqlDateConversionWithLocalDate() ).contains( localDate ); + } + + @ProcessorTest + public void testInstantToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForInstantConversionWithString( Optional.ofNullable( Instant.ofEpochSecond( 42L ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isEqualTo( "1970-01-01T00:00:42Z" ); + } + + @ProcessorTest + public void testInstantToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForInstantConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToInstantMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( "1970-01-01T00:00:00.000Z" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForInstantConversionWithString() ).contains( Instant.EPOCH ); + } + + @ProcessorTest + public void testStringToInstantNullMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForInstantConversionWithString() ).isEmpty(); + } + + @ProcessorTest + public void testPeriodToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForPeriodConversionWithString( Optional.ofNullable( Period.ofDays( 42 ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isEqualTo( "P42D" ); + } + + @ProcessorTest + public void testPeriodToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForPeriodConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToPeriodMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( "P1Y2M3D" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForPeriodConversionWithString() ).contains( Period.of( 1, 2, 3 ) ); + } + + @ProcessorTest + public void testStringToPeriodNullMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForPeriodConversionWithString() ).isEmpty(); + } + + @ProcessorTest + public void testDurationToStringMapping() { + OptionalSource source = new OptionalSource(); + source.setForDurationConversionWithString( Optional.ofNullable( Duration.ofMinutes( 42L ) ) ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isEqualTo( "PT42M" ); + } + + @ProcessorTest + public void testDurationToStringNullMapping() { + OptionalSource source = new OptionalSource(); + source.setForDurationConversionWithString( Optional.empty() ); + + Target target = OptionalSourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isNull(); + } + + @ProcessorTest + public void testStringToDurationMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( "PT20.345S" ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDurationConversionWithString() ).contains( Duration.ofSeconds( 20L, 345000000L ) ); + } + + @ProcessorTest + public void testStringToDurationNullMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( null ); + + OptionalSource source = OptionalSourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDurationConversionWithString() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java index ab80eed6d4..4eab59fbe3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Java8TimeConversionTest.java @@ -1,48 +1,42 @@ -/** - * Copyright 2012-2015 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.test.conversion.java8time; +import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.Period; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Calendar; +import java.util.Date; import java.util.TimeZone; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** - * + * Tests for conversions to/from Java 8 date and time types. */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) @IssueKey("121") public class Java8TimeConversionTest { - @Test + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( SourceTargetMapper.class ); + + @ProcessorTest public void testDateTimeToString() { Source src = new Source(); src.setZonedDateTime( ZonedDateTime.of( java.time.LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ) ); @@ -51,7 +45,7 @@ public void testDateTimeToString() { assertThat( target.getZonedDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); } - @Test + @ProcessorTest public void testLocalDateTimeToString() { Source src = new Source(); src.setLocalDateTime( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); @@ -60,7 +54,7 @@ public void testLocalDateTimeToString() { assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); } - @Test + @ProcessorTest public void testLocalDateToString() { Source src = new Source(); src.setLocalDate( LocalDate.of( 2014, 1, 1 ) ); @@ -69,7 +63,7 @@ public void testLocalDateToString() { assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); } - @Test + @ProcessorTest public void testLocalTimeToString() { Source src = new Source(); src.setLocalTime( LocalTime.of( 0, 0 ) ); @@ -78,7 +72,7 @@ public void testLocalTimeToString() { assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); } - @Test + @ProcessorTest public void testSourceToTargetMappingForStrings() { Source src = new Source(); src.setLocalTime( LocalTime.of( 0, 0 ) ); @@ -104,59 +98,59 @@ public void testSourceToTargetMappingForStrings() { assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); } - @Test + @ProcessorTest public void testStringToDateTime() { String dateTimeAsString = "01.01.2014 00:00 UTC"; Target target = new Target(); target.setZonedDateTime( dateTimeAsString ); ZonedDateTime sourceDateTime = - ZonedDateTime.of( java.time.LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); + ZonedDateTime.of( java.time.LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); Source src = SourceTargetMapper.INSTANCE.targetToSourceDateTimeMapped( target ); assertThat( src ).isNotNull(); assertThat( src.getZonedDateTime() ).isEqualTo( sourceDateTime ); } - @Test + @ProcessorTest public void testStringToLocalDateTime() { String dateTimeAsString = "01.01.2014 00:00"; Target target = new Target(); target.setLocalDateTime( dateTimeAsString ); LocalDateTime sourceDateTime = - LocalDateTime.of( 2014, 1, 1, 0, 0, 0 ); + LocalDateTime.of( 2014, 1, 1, 0, 0, 0 ); Source src = SourceTargetMapper.INSTANCE.targetToSourceLocalDateTimeMapped( target ); assertThat( src ).isNotNull(); assertThat( src.getLocalDateTime() ).isEqualTo( sourceDateTime ); } - @Test + @ProcessorTest public void testStringToLocalDate() { String dateTimeAsString = "01.01.2014"; Target target = new Target(); target.setLocalDate( dateTimeAsString ); LocalDate sourceDate = - LocalDate.of( 2014, 1, 1 ); + LocalDate.of( 2014, 1, 1 ); Source src = SourceTargetMapper.INSTANCE.targetToSourceLocalDateMapped( target ); assertThat( src ).isNotNull(); assertThat( src.getLocalDate() ).isEqualTo( sourceDate ); } - @Test + @ProcessorTest public void testStringToLocalTime() { String dateTimeAsString = "00:00"; Target target = new Target(); target.setLocalTime( dateTimeAsString ); LocalTime sourceTime = - LocalTime.of( 0, 0 ); + LocalTime.of( 0, 0 ); Source src = SourceTargetMapper.INSTANCE.targetToSourceLocalTimeMapped( target ); assertThat( src ).isNotNull(); assertThat( src.getLocalTime() ).isEqualTo( sourceTime ); } - @Test + @ProcessorTest public void testTargetToSourceNullMapping() { Target target = new Target(); Source src = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -168,7 +162,7 @@ public void testTargetToSourceNullMapping() { assertThat( src.getLocalTime() ).isNull(); } - @Test + @ProcessorTest public void testTargetToSourceMappingForStrings() { Target target = new Target(); @@ -180,34 +174,35 @@ public void testTargetToSourceMappingForStrings() { Source src = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( src.getZonedDateTime() ).isEqualTo( - ZonedDateTime.of( - java.time.LocalDateTime.of( - 2014, - 1, - 1, - 0, - 0 ), ZoneId.of( "UTC" ) ) ); + ZonedDateTime.of( + java.time.LocalDateTime.of( + 2014, + 1, + 1, + 0, + 0 + ), ZoneId.of( "UTC" ) ) ); assertThat( src.getLocalDateTime() ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); assertThat( src.getLocalDate() ).isEqualTo( LocalDate.of( 2014, 1, 1 ) ); assertThat( src.getLocalTime() ).isEqualTo( LocalTime.of( 0, 0 ) ); } - @Test + @ProcessorTest public void testCalendarMapping() { Source source = new Source(); ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); source.setForCalendarConversion( - dateTime ); + dateTime ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target.getForCalendarConversion() ).isNotNull(); assertThat( target.getForCalendarConversion().getTimeZone() ).isEqualTo( - TimeZone.getTimeZone( - "UTC" ) ); + TimeZone.getTimeZone( + "UTC" ) ); assertThat( target.getForCalendarConversion().get( Calendar.YEAR ) ).isEqualTo( dateTime.getYear() ); assertThat( target.getForCalendarConversion().get( Calendar.MONTH ) ).isEqualTo( - dateTime.getMonthValue() - 1 ); + dateTime.getMonthValue() - 1 ); assertThat( target.getForCalendarConversion().get( Calendar.DATE ) ).isEqualTo( dateTime.getDayOfMonth() ); assertThat( target.getForCalendarConversion().get( Calendar.MINUTE ) ).isEqualTo( dateTime.getMinute() ); assertThat( target.getForCalendarConversion().get( Calendar.HOUR ) ).isEqualTo( dateTime.getHour() ); @@ -217,13 +212,13 @@ public void testCalendarMapping() { assertThat( source.getForCalendarConversion() ).isEqualTo( dateTime ); } - @Test + @ProcessorTest + @DefaultTimeZone("UTC") public void testZonedDateTimeToDateMapping() { - TimeZone.setDefault( TimeZone.getTimeZone( "UTC" ) ); Source source = new Source(); ZonedDateTime dateTime = ZonedDateTime.of( LocalDateTime.of( 2014, 1, 1, 0, 0 ), ZoneId.of( "UTC" ) ); source.setForDateConversionWithZonedDateTime( - dateTime ); + dateTime ); Target target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); assertThat( target.getForDateConversionWithZonedDateTime() ).isNotNull(); @@ -240,12 +235,42 @@ public void testZonedDateTimeToDateMapping() { source = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( source.getForDateConversionWithZonedDateTime() ).isEqualTo( dateTime ); + } + + @ProcessorTest + public void testInstantToDateMapping() { + Instant instant = Instant.ofEpochMilli( 1539366615000L ); + Source source = new Source(); + source.setForDateConversionWithInstant( instant ); + Target target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + Date date = target.getForDateConversionWithInstant(); + assertThat( date ).isNotNull(); + assertThat( date.getTime() ).isEqualTo( 1539366615000L ); + + source = SourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForDateConversionWithInstant() ).isEqualTo( instant ); } - @Test + @ProcessorTest + public void testLocalDateTimeToLocalDateMapping() { + LocalDate localDate = LocalDate.of( 2014, 1, 1 ); + + Source source = new Source(); + source.setForLocalDateTimeConversionWithLocalDate( localDate ); + Target target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + LocalDateTime localDateTime = target.getForLocalDateTimeConversionWithLocalDate(); + assertThat( localDateTime ).isNotNull(); + assertThat( localDateTime ).isEqualTo( LocalDateTime.of( 2014, 1, 1, 0, 0 ) ); + + source = SourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source.getForLocalDateTimeConversionWithLocalDate() ).isEqualTo( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") public void testLocalDateTimeToDateMapping() { - TimeZone.setDefault( TimeZone.getTimeZone( "UTC" ) ); + Source source = new Source(); LocalDateTime dateTime = LocalDateTime.of( 2014, 1, 1, 0, 0 ); source.setForDateConversionWithLocalDateTime( dateTime ); @@ -266,7 +291,173 @@ public void testLocalDateTimeToDateMapping() { source = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( source.getForDateConversionWithLocalDateTime() ).isEqualTo( dateTime ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToDateMapping() { + + Source source = new Source(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForDateConversionWithLocalDate( localDate ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + assertThat( target.getForDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = SourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForDateConversionWithLocalDate() ).isEqualTo( localDate ); + } + + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") + public void testLocalDateToSqlDateMapping() { + + Source source = new Source(); + LocalDate localDate = LocalDate.of( 2016, 3, 1 ); + source.setForSqlDateConversionWithLocalDate( localDate ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( source ); + + assertThat( target.getForSqlDateConversionWithLocalDate() ).isNotNull(); + + Calendar instance = Calendar.getInstance( TimeZone.getTimeZone( "UTC" ) ); + instance.setTimeInMillis( target.getForSqlDateConversionWithLocalDate().getTime() ); + + assertThat( instance.get( Calendar.YEAR ) ).isEqualTo( localDate.getYear() ); + assertThat( instance.get( Calendar.MONTH ) ).isEqualTo( localDate.getMonthValue() - 1 ); + assertThat( instance.get( Calendar.DATE ) ).isEqualTo( localDate.getDayOfMonth() ); + + source = SourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source.getForSqlDateConversionWithLocalDate() ).isEqualTo( localDate ); + } + + @ProcessorTest + public void testInstantToStringMapping() { + Source source = new Source(); + source.setForInstantConversionWithString( Instant.ofEpochSecond( 42L ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isEqualTo( "1970-01-01T00:00:42Z" ); } + @ProcessorTest + public void testInstantToStringNullMapping() { + Source source = new Source(); + source.setForInstantConversionWithString( null ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForInstantConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToInstantMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( "1970-01-01T00:00:00.000Z" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Instant instant = source.getForInstantConversionWithString(); + assertThat( instant ).isEqualTo( Instant.EPOCH ); + } + + @ProcessorTest + public void testStringToInstantNullMapping() { + Target target = new Target(); + target.setForInstantConversionWithString( null ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Instant instant = source.getForInstantConversionWithString(); + assertThat( instant ).isNull(); + } + + @ProcessorTest + public void testPeriodToStringMapping() { + Source source = new Source(); + source.setForPeriodConversionWithString( Period.ofDays( 42 ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isEqualTo( "P42D" ); + } + + @ProcessorTest + public void testPeriodToStringNullMapping() { + Source source = new Source(); + source.setForPeriodConversionWithString( null ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String periodString = target.getForPeriodConversionWithString(); + assertThat( periodString ).isNull(); + } + + @ProcessorTest + public void testStringToPeriodMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( "P1Y2M3D" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Period period = source.getForPeriodConversionWithString(); + assertThat( period ).isEqualTo( Period.of( 1, 2, 3 ) ); + } + + @ProcessorTest + public void testStringToPeriodNullMapping() { + Target target = new Target(); + target.setForPeriodConversionWithString( null ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Period period = source.getForPeriodConversionWithString(); + assertThat( period ).isNull(); + } + + @ProcessorTest + public void testDurationToStringMapping() { + Source source = new Source(); + source.setForDurationConversionWithString( Duration.ofMinutes( 42L ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isEqualTo( "PT42M" ); + } + + @ProcessorTest + public void testDurationToStringNullMapping() { + Source source = new Source(); + source.setForDurationConversionWithString( null ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + String durationString = target.getForDurationConversionWithString(); + assertThat( durationString ).isNull(); + } + + @ProcessorTest + public void testStringToDurationMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( "PT20.345S" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Duration duration = source.getForDurationConversionWithString(); + assertThat( duration ).isEqualTo( Duration.ofSeconds( 20L, 345000000L ) ); + } + + @ProcessorTest + public void testStringToDurationNullMapping() { + Target target = new Target(); + target.setForDurationConversionWithString( null ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Duration duration = source.getForDurationConversionWithString(); + assertThat( duration ).isNull(); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/LocalDateTimeToXMLGregorianCalendarConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/LocalDateTimeToXMLGregorianCalendarConversionTest.java new file mode 100644 index 0000000000..5f656f2f76 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/LocalDateTimeToXMLGregorianCalendarConversionTest.java @@ -0,0 +1,116 @@ +/* + * 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.test.conversion.java8time; + +import java.time.LocalDateTime; +import java.util.Calendar; +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + +import org.mapstruct.ap.test.conversion.java8time.localdatetimetoxmlgregoriancalendarconversion.Source; +import org.mapstruct.ap.test.conversion.java8time.localdatetimetoxmlgregoriancalendarconversion.SourceTargetMapper; +import org.mapstruct.ap.test.conversion.java8time.localdatetimetoxmlgregoriancalendarconversion.Target; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Andrei Arlou + */ +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) +public class LocalDateTimeToXMLGregorianCalendarConversionTest { + + @ProcessorTest + public void shouldNullCheckOnConversionToTarget() { + Target target = SourceTargetMapper.INSTANCE.toTarget( new Source() ); + + assertThat( target ).isNotNull(); + assertThat( target.getLocalDateTime() ).isNull(); + } + + @ProcessorTest + public void shouldNullCheckOnConversionToSource() { + Source source = SourceTargetMapper.INSTANCE.toSource( new Target() ); + + assertThat( source ).isNotNull(); + assertThat( source.getXmlGregorianCalendar() ).isNull(); + } + + @ProcessorTest + public void shouldMapLocalDateTimeToXmlGregorianCalendarCorrectlyWithNanoseconds() + throws DatatypeConfigurationException { + LocalDateTime localDateTime = LocalDateTime.of( 1994, Calendar.MARCH, 5, 11, 30, 50, 9000000 ); + Target target = new Target(); + target.setLocalDateTime( localDateTime ); + + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + + XMLGregorianCalendar expectedCalendar = DatatypeFactory.newInstance() + .newXMLGregorianCalendar( 1994, Calendar.MARCH, 5, 11, 30, 50, 9, + DatatypeConstants.FIELD_UNDEFINED + ); + + assertThat( source ).isNotNull(); + assertThat( source.getXmlGregorianCalendar() ).isEqualTo( expectedCalendar ); + } + + @ProcessorTest + public void shouldMapLocalDateTimeToXmlGregorianCalendarCorrectlyWithSeconds() + throws DatatypeConfigurationException { + LocalDateTime localDateTime = LocalDateTime.of( 1994, Calendar.MARCH, 5, 11, 30, 50 ); + Target target = new Target(); + target.setLocalDateTime( localDateTime ); + + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + + XMLGregorianCalendar expectedCalendar = DatatypeFactory.newInstance() + .newXMLGregorianCalendar( 1994, Calendar.MARCH, 5, 11, 30, 50, 0, + DatatypeConstants.FIELD_UNDEFINED + ); + + assertThat( source ).isNotNull(); + assertThat( source.getXmlGregorianCalendar() ).isEqualTo( expectedCalendar ); + } + + @ProcessorTest + public void shouldMapLocalDateTimeToXmlGregorianCalendarCorrectlyWithMinutes() + throws DatatypeConfigurationException { + LocalDateTime localDateTime = LocalDateTime.of( 1994, Calendar.MARCH, 5, 11, 30 ); + Target target = new Target(); + target.setLocalDateTime( localDateTime ); + + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + + XMLGregorianCalendar expectedCalendar = DatatypeFactory.newInstance() + .newXMLGregorianCalendar( 1994, Calendar.MARCH, 5, 11, 30, 0, 0, + DatatypeConstants.FIELD_UNDEFINED + ); + + assertThat( source ).isNotNull(); + assertThat( source.getXmlGregorianCalendar() ).isEqualTo( expectedCalendar ); + } + + @ProcessorTest + public void shouldMapXmlGregorianCalendarToLocalDateTimeCorrectly() throws DatatypeConfigurationException { + XMLGregorianCalendar xmlGregorianCalendar = DatatypeFactory.newInstance() + .newXMLGregorianCalendar( 1994, Calendar.MARCH, 5, 11, 30, 50, 500, + DatatypeConstants.FIELD_UNDEFINED + ); + + Source source = new Source(); + source.setXmlGregorianCalendar( xmlGregorianCalendar ); + + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + + LocalDateTime expectedLocalDateTime = LocalDateTime.of( 1994, Calendar.MARCH, 5, 11, 30, 50, 500000000 ); + + assertThat( target ).isNotNull(); + assertThat( target.getLocalDateTime() ).isEqualTo( expectedLocalDateTime ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/LocalDateToXMLGregorianCalendarConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/LocalDateToXMLGregorianCalendarConversionTest.java new file mode 100644 index 0000000000..935e12d55d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/LocalDateToXMLGregorianCalendarConversionTest.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.ap.test.conversion.java8time; + +import java.time.LocalDate; +import javax.xml.datatype.DatatypeConstants; +import javax.xml.datatype.DatatypeFactory; +import javax.xml.datatype.XMLGregorianCalendar; + +import org.mapstruct.ap.test.conversion.java8time.localdatetoxmlgregoriancalendarconversion.Source; +import org.mapstruct.ap.test.conversion.java8time.localdatetoxmlgregoriancalendarconversion.SourceTargetMapper; +import org.mapstruct.ap.test.conversion.java8time.localdatetoxmlgregoriancalendarconversion.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Andreas Gudian + */ +@IssueKey("580") +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) +public class LocalDateToXMLGregorianCalendarConversionTest { + + @ProcessorTest + public void shouldNullCheckOnBuiltinAndConversion() { + Target target = SourceTargetMapper.INSTANCE.toTarget( new Source() ); + + assertThat( target ).isNotNull(); + assertThat( target.getDate() ).isNull(); + + Source source = SourceTargetMapper.INSTANCE.toSource( new Target() ); + + assertThat( source ).isNotNull(); + assertThat( source.getDate() ).isNull(); + } + + @ProcessorTest + public void shouldMapCorrectlyOnBuiltinAndConversion() throws Exception { + XMLGregorianCalendar calendarDate = DatatypeFactory.newInstance().newXMLGregorianCalendarDate( + 2007, + 11, + 14, + DatatypeConstants.FIELD_UNDEFINED ); + + LocalDate localDate = LocalDate.of( 2007, 11, 14 ); + + Source s1 = new Source(); + s1.setDate( calendarDate ); + Target target = SourceTargetMapper.INSTANCE.toTarget( s1 ); + + assertThat( target ).isNotNull(); + assertThat( target.getDate() ).isEqualTo( localDate ); + + + Target t1 = new Target(); + t1.setDate( localDate ); + Source source = SourceTargetMapper.INSTANCE.toSource( t1 ); + + assertThat( source ).isNotNull(); + assertThat( source.getDate() ).isEqualTo( calendarDate ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java new file mode 100644 index 0000000000..cf94f31869 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSource.java @@ -0,0 +1,160 @@ +/* + * 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.test.conversion.java8time; + +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZonedDateTime; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OptionalSource { + + private Optional zonedDateTime = Optional.empty(); + + private Optional localDateTime = Optional.empty(); + + private Optional localDate = Optional.empty(); + + private Optional localTime = Optional.empty(); + + private Optional forCalendarConversion = Optional.empty(); + + private Optional forDateConversionWithZonedDateTime = Optional.empty(); + + private Optional forDateConversionWithLocalDateTime = Optional.empty(); + + private Optional forDateConversionWithLocalDate = Optional.empty(); + + private Optional forSqlDateConversionWithLocalDate = Optional.empty(); + + private Optional forDateConversionWithInstant = Optional.empty(); + + private Optional forLocalDateTimeConversionWithLocalDate = Optional.empty(); + + private Optional forInstantConversionWithString = Optional.empty(); + + private Optional forPeriodConversionWithString = Optional.empty(); + + private Optional forDurationConversionWithString = Optional.empty(); + + public Optional getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(Optional dateTime) { + this.zonedDateTime = dateTime; + } + + public Optional getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(Optional localDateTime) { + this.localDateTime = localDateTime; + } + + public Optional getLocalDate() { + return localDate; + } + + public void setLocalDate(Optional localDate) { + this.localDate = localDate; + } + + public Optional getLocalTime() { + return localTime; + } + + public void setLocalTime(Optional localTime) { + this.localTime = localTime; + } + + public Optional getForCalendarConversion() { + return forCalendarConversion; + } + + public void setForCalendarConversion(Optional forCalendarConversion) { + this.forCalendarConversion = forCalendarConversion; + } + + public Optional getForDateConversionWithZonedDateTime() { + return forDateConversionWithZonedDateTime; + } + + public void setForDateConversionWithZonedDateTime(Optional forDateConversionWithZonedDateTime) { + this.forDateConversionWithZonedDateTime = forDateConversionWithZonedDateTime; + } + + public Optional getForDateConversionWithLocalDateTime() { + return forDateConversionWithLocalDateTime; + } + + public void setForDateConversionWithLocalDateTime(Optional forDateConversionWithLocalDateTime) { + this.forDateConversionWithLocalDateTime = forDateConversionWithLocalDateTime; + } + + public Optional getForDateConversionWithLocalDate() { + return forDateConversionWithLocalDate; + } + + public void setForDateConversionWithLocalDate(Optional forDateConversionWithLocalDate) { + this.forDateConversionWithLocalDate = forDateConversionWithLocalDate; + } + + public Optional getForSqlDateConversionWithLocalDate() { + return forSqlDateConversionWithLocalDate; + } + + public void setForSqlDateConversionWithLocalDate(Optional forSqlDateConversionWithLocalDate) { + this.forSqlDateConversionWithLocalDate = forSqlDateConversionWithLocalDate; + } + + public Optional getForDateConversionWithInstant() { + return forDateConversionWithInstant; + } + + public void setForDateConversionWithInstant(Optional forDateConversionWithInstant) { + this.forDateConversionWithInstant = forDateConversionWithInstant; + } + + public Optional getForLocalDateTimeConversionWithLocalDate() { + return forLocalDateTimeConversionWithLocalDate; + } + + public void setForLocalDateTimeConversionWithLocalDate( + Optional forLocalDateTimeConversionWithLocalDate) { + this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate; + } + + public Optional getForInstantConversionWithString() { + return forInstantConversionWithString; + } + + public void setForInstantConversionWithString(Optional forInstantConversionWithString) { + this.forInstantConversionWithString = forInstantConversionWithString; + } + + public Optional getForPeriodConversionWithString() { + return forPeriodConversionWithString; + } + + public void setForPeriodConversionWithString(Optional forPeriodConversionWithString) { + this.forPeriodConversionWithString = forPeriodConversionWithString; + } + + public Optional getForDurationConversionWithString() { + return forDurationConversionWithString; + } + + public void setForDurationConversionWithString(Optional forDurationConversionWithString) { + this.forDurationConversionWithString = forDurationConversionWithString; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java new file mode 100644 index 0000000000..a266309b61 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/OptionalSourceTargetMapper.java @@ -0,0 +1,70 @@ +/* + * 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.test.conversion.java8time; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalSourceTargetMapper { + + String DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm z"; + + String LOCAL_DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm"; + + String LOCAL_DATE_FORMAT = "dd.MM.yyyy"; + + String LOCAL_TIME_FORMAT = "HH:mm"; + + OptionalSourceTargetMapper INSTANCE = Mappers.getMapper( OptionalSourceTargetMapper.class ); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTarget(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTargetDefaultMapping(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + Target sourceToTargetDateTimeMapped(OptionalSource source); + + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + Target sourceToTargetLocalDateTimeMapped(OptionalSource source); + + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + Target sourceToTargetLocalDateMapped(OptionalSource source); + + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + Target sourceToTargetLocalTimeMapped(OptionalSource source); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + OptionalSource targetToSource(Target target); + + @Mapping(target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT) + OptionalSource targetToSourceDateTimeMapped(Target target); + + @Mapping(target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT) + OptionalSource targetToSourceLocalDateTimeMapped(Target target); + + @Mapping(target = "localDate", dateFormat = LOCAL_DATE_FORMAT) + OptionalSource targetToSourceLocalDateMapped(Target target); + + @Mapping(target = "localTime", dateFormat = LOCAL_TIME_FORMAT) + OptionalSource targetToSourceLocalTimeMapped(Target target); + + @InheritInverseConfiguration(name = "sourceToTarget") + OptionalSource targetToSourceDefaultMapping(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java index ec526fe639..93479f8a86 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Source.java @@ -1,26 +1,16 @@ -/** - * Copyright 2012-2015 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.conversion.java8time; +import java.time.Duration; +import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.Period; import java.time.ZonedDateTime; /** @@ -42,6 +32,20 @@ public class Source { private LocalDateTime forDateConversionWithLocalDateTime; + private LocalDate forDateConversionWithLocalDate; + + private LocalDate forSqlDateConversionWithLocalDate; + + private Instant forDateConversionWithInstant; + + private LocalDate forLocalDateTimeConversionWithLocalDate; + + private Instant forInstantConversionWithString; + + private Period forPeriodConversionWithString; + + private Duration forDurationConversionWithString; + public ZonedDateTime getZonedDateTime() { return zonedDateTime; } @@ -97,4 +101,60 @@ public LocalDateTime getForDateConversionWithLocalDateTime() { public void setForDateConversionWithLocalDateTime(LocalDateTime forDateConversionWithLocalDateTime) { this.forDateConversionWithLocalDateTime = forDateConversionWithLocalDateTime; } + + public LocalDate getForDateConversionWithLocalDate() { + return forDateConversionWithLocalDate; + } + + public void setForDateConversionWithLocalDate(LocalDate forDateConversionWithLocalDate) { + this.forDateConversionWithLocalDate = forDateConversionWithLocalDate; + } + + public LocalDate getForSqlDateConversionWithLocalDate() { + return forSqlDateConversionWithLocalDate; + } + + public void setForSqlDateConversionWithLocalDate(LocalDate forSqlDateConversionWithLocalDate) { + this.forSqlDateConversionWithLocalDate = forSqlDateConversionWithLocalDate; + } + + public Instant getForDateConversionWithInstant() { + return forDateConversionWithInstant; + } + + public void setForDateConversionWithInstant(Instant forDateConversionWithInstant) { + this.forDateConversionWithInstant = forDateConversionWithInstant; + } + + public LocalDate getForLocalDateTimeConversionWithLocalDate() { + return forLocalDateTimeConversionWithLocalDate; + } + + public void setForLocalDateTimeConversionWithLocalDate(LocalDate forLocalDateTimeConversionWithLocalDate) { + this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate; + } + + public Instant getForInstantConversionWithString() { + return forInstantConversionWithString; + } + + public void setForInstantConversionWithString(Instant forInstantConversionWithString) { + this.forInstantConversionWithString = forInstantConversionWithString; + } + + public Period getForPeriodConversionWithString() { + return forPeriodConversionWithString; + } + + public void setForPeriodConversionWithString(Period forPeriodConversionWithString) { + this.forPeriodConversionWithString = forPeriodConversionWithString; + } + + public Duration getForDurationConversionWithString() { + return forDurationConversionWithString; + } + + public void setForDurationConversionWithString(Duration forDurationConversionWithString) { + this.forDurationConversionWithString = forDurationConversionWithString; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java index 1a46019c86..f2351f4175 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.test.conversion.java8time; @@ -39,6 +26,7 @@ public interface SourceTargetMapper { String LOCAL_TIME_FORMAT = "HH:mm"; SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + @Mappings( { @Mapping( target = "zonedDateTime", dateFormat = DATE_TIME_FORMAT ), @Mapping( target = "localDateTime", dateFormat = LOCAL_DATE_TIME_FORMAT ), @Mapping( target = "localDate", dateFormat = LOCAL_DATE_FORMAT ), diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java index 349b9b6664..d2a4878731 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/Target.java @@ -1,25 +1,14 @@ -/** - * Copyright 2012-2015 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.conversion.java8time; +import java.time.LocalDateTime; import java.util.Calendar; import java.util.Date; + /** * */ @@ -39,6 +28,20 @@ public class Target { private Date forDateConversionWithLocalDateTime; + private Date forDateConversionWithLocalDate; + + private java.sql.Date forSqlDateConversionWithLocalDate; + + private Date forDateConversionWithInstant; + + private LocalDateTime forLocalDateTimeConversionWithLocalDate; + + private String forInstantConversionWithString; + + private String forPeriodConversionWithString; + + private String forDurationConversionWithString; + public String getZonedDateTime() { return zonedDateTime; } @@ -95,4 +98,59 @@ public void setForDateConversionWithLocalDateTime(Date forDateConversionWithLoca this.forDateConversionWithLocalDateTime = forDateConversionWithLocalDateTime; } + public Date getForDateConversionWithLocalDate() { + return forDateConversionWithLocalDate; + } + + public void setForDateConversionWithLocalDate(Date forDateConversionWithLocalDate) { + this.forDateConversionWithLocalDate = forDateConversionWithLocalDate; + } + + public java.sql.Date getForSqlDateConversionWithLocalDate() { + return forSqlDateConversionWithLocalDate; + } + + public void setForSqlDateConversionWithLocalDate(java.sql.Date forSqlDateConversionWithLocalDate) { + this.forSqlDateConversionWithLocalDate = forSqlDateConversionWithLocalDate; + } + + public Date getForDateConversionWithInstant() { + return forDateConversionWithInstant; + } + + public void setForDateConversionWithInstant(Date forDateConversionWithInstant) { + this.forDateConversionWithInstant = forDateConversionWithInstant; + } + + public LocalDateTime getForLocalDateTimeConversionWithLocalDate() { + return forLocalDateTimeConversionWithLocalDate; + } + + public void setForLocalDateTimeConversionWithLocalDate(LocalDateTime forLocalDateTimeConversionWithLocalDate) { + this.forLocalDateTimeConversionWithLocalDate = forLocalDateTimeConversionWithLocalDate; + } + + public String getForInstantConversionWithString() { + return forInstantConversionWithString; + } + + public void setForInstantConversionWithString(String forInstantConversionWithString) { + this.forInstantConversionWithString = forInstantConversionWithString; + } + + public String getForPeriodConversionWithString() { + return forPeriodConversionWithString; + } + + public void setForPeriodConversionWithString(String forPeriodConversionWithString) { + this.forPeriodConversionWithString = forPeriodConversionWithString; + } + + public String getForDurationConversionWithString() { + return forDurationConversionWithString; + } + + public void setForDurationConversionWithString(String forDurationConversionWithString) { + this.forDurationConversionWithString = forDurationConversionWithString; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/ZonedOffsetDateTimeToLocalDateTimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/ZonedOffsetDateTimeToLocalDateTimeConversionTest.java new file mode 100644 index 0000000000..ab17980e96 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/ZonedOffsetDateTimeToLocalDateTimeConversionTest.java @@ -0,0 +1,113 @@ +/* + * 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.test.conversion.java8time; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion.Source; +import org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion.SourceTargetMapper; +import org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) +@IssueKey("4027") +public class ZonedOffsetDateTimeToLocalDateTimeConversionTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( SourceTargetMapper.class ); + + @ProcessorTest + public void testZonedDateTimeToLocalDateTimeMapping() { + ZonedDateTime zonedDateTime = ZonedDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneId.of( "UTC" ) ); + Source source = new Source(); + source.setZonedDateTime( zonedDateTime ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + assertThat( target.getZonedDateTime() ) + .isEqualTo( LocalDateTime.of( 2024, 1, 1, 12, 30, 0 ) ); + } + + @ProcessorTest + public void testOffsetDateTimeToLocalDateTimeMapping() { + OffsetDateTime offsetDateTime = OffsetDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ); + Source source = new Source(); + source.setOffsetDateTime( offsetDateTime ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + assertThat( target.getOffsetDateTime() ) + .isEqualTo( LocalDateTime.of( 2024, 1, 1, 12, 30, 0 ) ); + } + + @ProcessorTest + public void testLocalDateTimeToZonedDateTimeMapping() { + LocalDateTime localDateTime = LocalDateTime.of( 2024, 1, 1, 12, 30, 0 ); + Target target = new Target(); + target.setZonedDateTime( localDateTime ); + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + assertThat( source.getZonedDateTime() ) + .isEqualTo( ZonedDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ) ); + } + + @ProcessorTest + public void testLocalDateTimeToOffsetDateTimeMapping() { + LocalDateTime localDateTime = LocalDateTime.of( 2024, 1, 1, 12, 30, 0 ); + Target target = new Target(); + target.setOffsetDateTime( localDateTime ); + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + assertThat( source.getOffsetDateTime() ) + .isEqualTo( OffsetDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ) ); + } + + @ProcessorTest + public void testZonedDateTimeToInstantMapping() { + ZonedDateTime zonedDateTime = ZonedDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ); + Source source = new Source(); + source.setZonedDateTimeAsInstant( zonedDateTime ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + assertThat( target.getZonedDateTimeAsInstant() ) + .isEqualTo( Instant.parse( "2024-01-01T12:30:00Z" ) ); + } + + @ProcessorTest + public void testOffsetDateTimeToInstantMapping() { + OffsetDateTime offsetDateTime = OffsetDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ); + Source source = new Source(); + source.setOffsetDateTimeAsInstant( offsetDateTime ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + assertThat( target.getOffsetDateTimeAsInstant() ) + .isEqualTo( Instant.parse( "2024-01-01T12:30:00Z" ) ); + } + + @ProcessorTest + public void testInstantToZonedDateTimeMapping() { + Instant instant = Instant.parse( "2024-01-01T12:30:00Z" ); + Target target = new Target(); + target.setZonedDateTimeAsInstant( instant ); + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + assertThat( source.getZonedDateTimeAsInstant() ) + .isEqualTo( ZonedDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ) ); + } + + @ProcessorTest + public void testInstantToOffsetDateTimeMapping() { + Instant instant = Instant.parse( "2024-01-01T12:30:00Z" ); + Target target = new Target(); + target.setOffsetDateTimeAsInstant( instant ); + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + assertThat( source.getOffsetDateTimeAsInstant() ) + .isEqualTo( OffsetDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/Source.java new file mode 100644 index 0000000000..f83721cbb5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/Source.java @@ -0,0 +1,40 @@ +/* + * 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.test.conversion.java8time.custompatterndatetimeformattergenerated; + +import java.time.LocalDateTime; + +public class Source { + + private LocalDateTime localDateTime1; + private LocalDateTime localDateTime2; + private LocalDateTime localDateTime3; + + public LocalDateTime getLocalDateTime1() { + return localDateTime1; + } + + public void setLocalDateTime1(LocalDateTime localDateTime1) { + this.localDateTime1 = localDateTime1; + } + + public LocalDateTime getLocalDateTime2() { + return localDateTime2; + } + + public void setLocalDateTime2(LocalDateTime localDateTime2) { + this.localDateTime2 = localDateTime2; + } + + public LocalDateTime getLocalDateTime3() { + return localDateTime3; + } + + public void setLocalDateTime3(LocalDateTime localDateTime3) { + this.localDateTime3 = localDateTime3; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/SourceTargetMapper.java new file mode 100644 index 0000000000..960ceec3b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/SourceTargetMapper.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.ap.test.conversion.java8time.custompatterndatetimeformattergenerated; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper { + + String LOCAL_DATE_TIME_FORMAT = "dd.MM.yyyy HH:mm"; + String VERY_SIMILAR_LOCAL_DATE_TIME_FORMAT = "dd.MM.yyyy HH.mm"; + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping(target = "localDateTime1", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDateTime2", dateFormat = LOCAL_DATE_TIME_FORMAT) + @Mapping(target = "localDateTime3", dateFormat = VERY_SIMILAR_LOCAL_DATE_TIME_FORMAT) + Target map(Source source); + + @InheritInverseConfiguration + Source map(Target target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/Target.java new file mode 100644 index 0000000000..7d265ba4aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/custompatterndatetimeformattergenerated/Target.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.test.conversion.java8time.custompatterndatetimeformattergenerated; + +public class Target { + + private String localDateTime1; + private String localDateTime2; + private String localDateTime3; + + public String getLocalDateTime1() { + return localDateTime1; + } + + public void setLocalDateTime1(String localDateTime1) { + this.localDateTime1 = localDateTime1; + } + + public String getLocalDateTime2() { + return localDateTime2; + } + + public void setLocalDateTime2(String localDateTime2) { + this.localDateTime2 = localDateTime2; + } + + public String getLocalDateTime3() { + return localDateTime3; + } + + public void setLocalDateTime3(String localDateTime3) { + this.localDateTime3 = localDateTime3; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/Source.java new file mode 100644 index 0000000000..d79ad6072d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/Source.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.ap.test.conversion.java8time.localdatetimetoxmlgregoriancalendarconversion; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * @author Andrei Arlou + */ +public class Source { + private XMLGregorianCalendar xmlGregorianCalendar; + + public XMLGregorianCalendar getXmlGregorianCalendar() { + return xmlGregorianCalendar; + } + + public void setXmlGregorianCalendar(XMLGregorianCalendar xmlGregorianCalendar) { + this.xmlGregorianCalendar = xmlGregorianCalendar; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/SourceTargetMapper.java new file mode 100644 index 0000000000..ed4c1d374d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/SourceTargetMapper.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.ap.test.conversion.java8time.localdatetimetoxmlgregoriancalendarconversion; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Andrei Arlou + */ +@Mapper +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + @Mapping(target = "localDateTime", source = "xmlGregorianCalendar") + Target toTarget(Source source); + + @Mapping(target = "xmlGregorianCalendar", source = "localDateTime") + Source toSource(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/Target.java new file mode 100644 index 0000000000..b8fc51e6d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetimetoxmlgregoriancalendarconversion/Target.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.ap.test.conversion.java8time.localdatetimetoxmlgregoriancalendarconversion; + +import java.time.LocalDateTime; + +/** + * @author Andrei Arlou + */ +public class Target { + private LocalDateTime localDateTime; + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/Source.java new file mode 100644 index 0000000000..081fc1edb9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/Source.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.ap.test.conversion.java8time.localdatetoxmlgregoriancalendarconversion; + +import javax.xml.datatype.XMLGregorianCalendar; + +/** + * @author Andreas Gudian + */ +public class Source { + private XMLGregorianCalendar date; + + public XMLGregorianCalendar getDate() { + return date; + } + + public void setDate(XMLGregorianCalendar date) { + this.date = date; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/SourceTargetMapper.java new file mode 100644 index 0000000000..5c8598241a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/SourceTargetMapper.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.ap.test.conversion.java8time.localdatetoxmlgregoriancalendarconversion; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + */ +@Mapper +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + Target toTarget(Source source); + + Source toSource(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/Target.java new file mode 100644 index 0000000000..6b9fd2bc78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/localdatetoxmlgregoriancalendarconversion/Target.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.ap.test.conversion.java8time.localdatetoxmlgregoriancalendarconversion; + +import java.time.LocalDate; + +/** + * @author Andreas Gudian + */ +public class Target { + + private LocalDate date; + + public LocalDate getDate() { + return date; + } + + public void setDate(LocalDate date) { + this.date = date; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Source.java new file mode 100644 index 0000000000..18bd99825c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Source.java @@ -0,0 +1,48 @@ +/* + * 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.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion; + +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; + +public class Source { + private ZonedDateTime zonedDateTime; + private OffsetDateTime offsetDateTime; + private ZonedDateTime zonedDateTimeAsInstant; + private OffsetDateTime offsetDateTimeAsInstant; + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public OffsetDateTime getOffsetDateTime() { + return offsetDateTime; + } + + public void setOffsetDateTime(OffsetDateTime offsetDateTime) { + this.offsetDateTime = offsetDateTime; + } + + public ZonedDateTime getZonedDateTimeAsInstant() { + return zonedDateTimeAsInstant; + } + + public void setZonedDateTimeAsInstant(ZonedDateTime zonedDateTimeAsInstant) { + this.zonedDateTimeAsInstant = zonedDateTimeAsInstant; + } + + public OffsetDateTime getOffsetDateTimeAsInstant() { + return offsetDateTimeAsInstant; + } + + public void setOffsetDateTimeAsInstant(OffsetDateTime offsetDateTimeAsInstant) { + this.offsetDateTimeAsInstant = offsetDateTimeAsInstant; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapper.java new file mode 100644 index 0000000000..43bee5f77f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapper.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.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper { + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + Target toTarget(Source source); + + Source toSource(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Target.java new file mode 100644 index 0000000000..b43b9f6843 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Target.java @@ -0,0 +1,48 @@ +/* + * 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.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion; + +import java.time.Instant; +import java.time.LocalDateTime; + +public class Target { + private LocalDateTime zonedDateTime; + private LocalDateTime offsetDateTime; + private Instant zonedDateTimeAsInstant; + private Instant offsetDateTimeAsInstant; + + public LocalDateTime getOffsetDateTime() { + return offsetDateTime; + } + + public void setOffsetDateTime(LocalDateTime offsetDateTime) { + this.offsetDateTime = offsetDateTime; + } + + public LocalDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(LocalDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public Instant getZonedDateTimeAsInstant() { + return zonedDateTimeAsInstant; + } + + public void setZonedDateTimeAsInstant(Instant zonedDateTimeAsInstant) { + this.zonedDateTimeAsInstant = zonedDateTimeAsInstant; + } + + public Instant getOffsetDateTimeAsInstant() { + return offsetDateTimeAsInstant; + } + + public void setOffsetDateTimeAsInstant(Instant offsetDateTimeAsInstant) { + this.offsetDateTimeAsInstant = offsetDateTimeAsInstant; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java index 19d6ae8160..d447c455ff 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/JodaConversionTest.java @@ -1,25 +1,11 @@ -/** - * Copyright 2012-2015 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.conversion.jodatime; import java.util.Calendar; -import java.util.Locale; import java.util.TimeZone; import org.joda.time.DateTime; @@ -27,31 +13,26 @@ import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junitpioneer.jupiter.DefaultLocale; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.WithJoda; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; /** * Tests the conversion between Joda-Time types and String/Date/Calendar. * * @author Timo Eckhardt */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) @IssueKey("75") +@DefaultLocale("de") +@WithJoda public class JodaConversionTest { - @Before - public void setDefaultLocale() { - Locale.setDefault( Locale.GERMAN ); - } - - @Test + @ProcessorTest public void testDateTimeToString() { Source src = new Source(); src.setDateTime( new DateTime( 2014, 1, 1, 0, 0, 0, DateTimeZone.UTC ) ); @@ -60,7 +41,7 @@ public void testDateTimeToString() { assertThat( target.getDateTime() ).isEqualTo( "01.01.2014 00:00 UTC" ); } - @Test + @ProcessorTest public void testLocalDateTimeToString() { Source src = new Source(); src.setLocalDateTime( new LocalDateTime( 2014, 1, 1, 0, 0 ) ); @@ -69,7 +50,7 @@ public void testLocalDateTimeToString() { assertThat( target.getLocalDateTime() ).isEqualTo( "01.01.2014 00:00" ); } - @Test + @ProcessorTest public void testLocalDateToString() { Source src = new Source(); src.setLocalDate( new LocalDate( 2014, 1, 1 ) ); @@ -78,7 +59,7 @@ public void testLocalDateToString() { assertThat( target.getLocalDate() ).isEqualTo( "01.01.2014" ); } - @Test + @ProcessorTest public void testLocalTimeToString() { Source src = new Source(); src.setLocalTime( new LocalTime( 0, 0 ) ); @@ -87,7 +68,7 @@ public void testLocalTimeToString() { assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); } - @Test + @ProcessorTest public void testSourceToTargetMappingForStrings() { Source src = new Source(); src.setLocalTime( new LocalTime( 0, 0 ) ); @@ -107,13 +88,13 @@ public void testSourceToTargetMappingForStrings() { // and now with default mappings target = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014 00:00:00 UTC" ); - assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014 00:00:00" ); + assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014, 00:00:00 UTC" ); + assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014, 00:00:00" ); assertThat( target.getLocalDate() ).isEqualTo( "1. Januar 2014" ); assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" ); } - @Test + @ProcessorTest public void testStringToDateTime() { String dateTimeAsString = "01.01.2014 00:00 UTC"; Target target = new Target(); @@ -126,7 +107,7 @@ public void testStringToDateTime() { assertThat( src.getDateTime() ).isEqualTo( sourceDateTime ); } - @Test + @ProcessorTest public void testStringToLocalDateTime() { String dateTimeAsString = "01.01.2014 00:00"; Target target = new Target(); @@ -139,7 +120,7 @@ public void testStringToLocalDateTime() { assertThat( src.getLocalDateTime() ).isEqualTo( sourceDateTime ); } - @Test + @ProcessorTest public void testStringToLocalDate() { String dateTimeAsString = "01.01.2014"; Target target = new Target(); @@ -152,7 +133,7 @@ public void testStringToLocalDate() { assertThat( src.getLocalDate() ).isEqualTo( sourceDate ); } - @Test + @ProcessorTest public void testStringToLocalTime() { String dateTimeAsString = "00:00"; Target target = new Target(); @@ -165,7 +146,7 @@ public void testStringToLocalTime() { assertThat( src.getLocalTime() ).isEqualTo( sourceTime ); } - @Test + @ProcessorTest public void testTargetToSourceNullMapping() { Target target = new Target(); Source src = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -177,7 +158,7 @@ public void testTargetToSourceNullMapping() { assertThat( src.getLocalTime() ).isNull(); } - @Test + @ProcessorTest public void testTargetToSourceMappingForStrings() { Target target = new Target(); @@ -194,7 +175,7 @@ public void testTargetToSourceMappingForStrings() { assertThat( src.getLocalTime() ).isEqualTo( new LocalTime( 0, 0 ) ); } - @Test + @ProcessorTest public void testCalendar() { Calendar calendar = Calendar.getInstance( TimeZone.getTimeZone( "CET" ) ); DateTime dateTimeWithCalendar = new DateTime( calendar ); @@ -210,7 +191,7 @@ public void testCalendar() { assertThat( mappedSource.getDateTimeForCalendarConversion() ).isEqualTo( dateTimeWithCalendar ); } - @Test + @ProcessorTest @WithClasses({ StringToLocalDateMapper.class, SourceWithStringDate.class, TargetWithLocalDate.class }) @IssueKey("456") public void testStringToLocalDateUsingDefaultFormat() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/Source.java index cd14ab058b..5c23318479 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.jodatime; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/SourceTargetMapper.java index 2c35a1e90b..d1c2723c5c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.jodatime; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/SourceWithStringDate.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/SourceWithStringDate.java index d1ce4ce7f5..ba209d3db4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/SourceWithStringDate.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/SourceWithStringDate.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.jodatime; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/StringToLocalDateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/StringToLocalDateMapper.java index 03395ea13a..52dfd76aca 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/StringToLocalDateMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/StringToLocalDateMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.jodatime; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/Target.java index b9507737c3..ef12f646cf 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.jodatime; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/TargetWithLocalDate.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/TargetWithLocalDate.java index dcd69c241e..93c4607cab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/TargetWithLocalDate.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/jodatime/TargetWithLocalDate.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.jodatime; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java new file mode 100644 index 0000000000..cba7785512 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleConversionTest.java @@ -0,0 +1,46 @@ +/* + * 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.test.conversion.locale; + +import java.util.Locale; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests conversions between {@link Locale} and String. + * + * @author Jason Bodnar + */ +@IssueKey("3172") +@WithClasses({ LocaleSource.class, LocaleTarget.class, LocaleMapper.class }) +public class LocaleConversionTest { + + @ProcessorTest + public void shouldApplyLocaleConversion() { + LocaleSource source = new LocaleSource(); + source.setLocaleA( Locale.getDefault() ); + + LocaleTarget target = LocaleMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getLocaleA() ).isEqualTo( source.getLocaleA().toLanguageTag() ); + } + + @ProcessorTest + public void shouldApplyReverseLocaleConversion() { + LocaleTarget target = new LocaleTarget(); + target.setLocaleA( Locale.getDefault().toLanguageTag() ); + + LocaleSource source = LocaleMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getLocaleA() ).isEqualTo( Locale.forLanguageTag( target.getLocaleA() ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.java new file mode 100644 index 0000000000..3da3fc4ae4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleMapper.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.ap.test.conversion.locale; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface LocaleMapper { + + LocaleMapper INSTANCE = Mappers.getMapper( LocaleMapper.class ); + + LocaleTarget sourceToTarget(LocaleSource source); + + LocaleSource targetToSource(LocaleTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.java new file mode 100644 index 0000000000..69ea5bda2d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleSource.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.ap.test.conversion.locale; + +import java.util.Locale; + +/** + * @author Jason Bodnar + */ +public class LocaleSource { + private Locale localeA; + + public Locale getLocaleA() { + return localeA; + } + + public void setLocaleA(Locale localeA) { + this.localeA = localeA; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.java new file mode 100644 index 0000000000..000ff86b05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/locale/LocaleTarget.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.ap.test.conversion.locale; + +/** + * @author Jason Bodnar + */ +public class LocaleTarget { + private String localeA; + + public String getLocaleA() { + return localeA; + } + + public void setLocaleA(String localeA) { + this.localeA = localeA; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java new file mode 100644 index 0000000000..3bc144511e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryDto.java @@ -0,0 +1,56 @@ +/* + * 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.test.conversion.lossy; + +public class CutleryInventoryDto { + + private short numberOfKnifes; + private int numberOfForks; + private byte numberOfSpoons; + private int drawerId; + + private float approximateKnifeLength; + + public short getNumberOfKnifes() { + return numberOfKnifes; + } + + public void setNumberOfKnifes(short numberOfKnifes) { + this.numberOfKnifes = numberOfKnifes; + } + + public int getNumberOfForks() { + return numberOfForks; + } + + public void setNumberOfForks(int numberOfForks) { + this.numberOfForks = numberOfForks; + } + + public byte getNumberOfSpoons() { + return numberOfSpoons; + } + + public void setNumberOfSpoons(byte numberOfSpoons) { + this.numberOfSpoons = numberOfSpoons; + } + + public float getApproximateKnifeLength() { + return approximateKnifeLength; + } + + public void setApproximateKnifeLength(float approximateKnifeLength) { + this.approximateKnifeLength = approximateKnifeLength; + } + + public int getDrawerId() { + return drawerId; + } + + public void setDrawerId(int drawerId) { + this.drawerId = drawerId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java new file mode 100644 index 0000000000..755dbc3637 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryEntity.java @@ -0,0 +1,56 @@ +/* + * 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.test.conversion.lossy; + +public class CutleryInventoryEntity { + + private int numberOfKnifes; + private Long numberOfForks; + private short numberOfSpoons; + private String drawerId; + + private double approximateKnifeLength; + + public int getNumberOfKnifes() { + return numberOfKnifes; + } + + public void setNumberOfKnifes(int numberOfKnifes) { + this.numberOfKnifes = numberOfKnifes; + } + + public Long getNumberOfForks() { + return numberOfForks; + } + + public void setNumberOfForks(Long numberOfForks) { + this.numberOfForks = numberOfForks; + } + + public short getNumberOfSpoons() { + return numberOfSpoons; + } + + public void setNumberOfSpoons(short numberOfSpoons) { + this.numberOfSpoons = numberOfSpoons; + } + + public double getApproximateKnifeLength() { + return approximateKnifeLength; + } + + public void setApproximateKnifeLength(double approximateKnifeLength) { + this.approximateKnifeLength = approximateKnifeLength; + } + + public String getDrawerId() { + return drawerId; + } + + public void setDrawerId(String drawerId) { + this.drawerId = drawerId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryMapper.java new file mode 100644 index 0000000000..258f696d5b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/CutleryInventoryMapper.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.ap.test.conversion.lossy; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface CutleryInventoryMapper { + + CutleryInventoryMapper INSTANCE = Mappers.getMapper( CutleryInventoryMapper.class ); + + CutleryInventoryEntity map( CutleryInventoryDto in ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper1.java new file mode 100644 index 0000000000..b96745c3cc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper1.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerMapper1 { + + ErroneousKitchenDrawerMapper1 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerMapper1.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfForks", source = "numberOfForks" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper3.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper3.java new file mode 100644 index 0000000000..1b270dc395 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper3.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR, uses = VerySpecialNumberMapper.class ) +public interface ErroneousKitchenDrawerMapper3 { + + ErroneousKitchenDrawerMapper3 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerMapper3.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfSpoons", source = "numberOfSpoons" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper4.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper4.java new file mode 100644 index 0000000000..fc02081182 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper4.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerMapper4 { + + ErroneousKitchenDrawerMapper4 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerMapper4.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "depth", source = "depth" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper5.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper5.java new file mode 100644 index 0000000000..ef86586060 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper5.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerMapper5 { + + ErroneousKitchenDrawerMapper5 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerMapper5.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "length", source = "length" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.java new file mode 100644 index 0000000000..c33f1bd662 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerMapper6.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerMapper6 { + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "drawerId", source = "drawerId" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.java new file mode 100644 index 0000000000..d83b368bce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper1.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper1 { + + ErroneousKitchenDrawerOptionalMapper1 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper1.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfForks", source = "numberOfForks" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.java new file mode 100644 index 0000000000..7d1824a5b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper3.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR, uses = VerySpecialNumberMapper.class ) +public interface ErroneousKitchenDrawerOptionalMapper3 { + + ErroneousKitchenDrawerOptionalMapper3 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper3.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfSpoons", source = "numberOfSpoons" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.java new file mode 100644 index 0000000000..edd33c71d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper4.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper4 { + + ErroneousKitchenDrawerOptionalMapper4 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper4.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "depth", source = "depth" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.java new file mode 100644 index 0000000000..bad921a7c2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper5.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper5 { + + ErroneousKitchenDrawerOptionalMapper5 INSTANCE = Mappers.getMapper( ErroneousKitchenDrawerOptionalMapper5.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "length", source = "length" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.java new file mode 100644 index 0000000000..acd2e0876f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ErroneousKitchenDrawerOptionalMapper6.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +@Mapper( typeConversionPolicy = ReportingPolicy.ERROR ) +public interface ErroneousKitchenDrawerOptionalMapper6 { + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "drawerId", source = "drawerId" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerMapper2.java new file mode 100644 index 0000000000..2ececec67b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerMapper2.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( typeConversionPolicy = ReportingPolicy.WARN ) +public interface KitchenDrawerMapper2 { + + KitchenDrawerMapper2 INSTANCE = Mappers.getMapper( KitchenDrawerMapper2.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfKnifes", source = "numberOfKnifes" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerMapper6.java new file mode 100644 index 0000000000..8932858109 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerMapper6.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( typeConversionPolicy = ReportingPolicy.WARN, uses = VerySpecialNumberMapper.class ) +public interface KitchenDrawerMapper6 { + + KitchenDrawerMapper6 INSTANCE = Mappers.getMapper( KitchenDrawerMapper6.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "height", source = "height" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.java new file mode 100644 index 0000000000..92862d3320 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper2.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.WARN ) +public interface KitchenDrawerOptionalMapper2 { + + KitchenDrawerOptionalMapper2 INSTANCE = Mappers.getMapper( KitchenDrawerOptionalMapper2.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "numberOfKnifes", source = "numberOfKnifes" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.java new file mode 100644 index 0000000000..2b3e982584 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/KitchenDrawerOptionalMapper6.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.ap.test.conversion.lossy; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( typeConversionPolicy = ReportingPolicy.WARN, uses = VerySpecialNumberMapper.class ) +public interface KitchenDrawerOptionalMapper6 { + + KitchenDrawerOptionalMapper6 INSTANCE = Mappers.getMapper( KitchenDrawerOptionalMapper6.class ); + + @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "height", source = "height" ) + RegularKitchenDrawerEntity map( OversizedKitchenDrawerOptionalDto dto ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ListMapper.java new file mode 100644 index 0000000000..b253a26f1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/ListMapper.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.ap.test.conversion.lossy; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( typeConversionPolicy = ReportingPolicy.WARN ) +public interface ListMapper { + + List map(List in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java new file mode 100644 index 0000000000..3468da4535 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java @@ -0,0 +1,264 @@ +/* + * 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.test.conversion.lossy; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.withinPercentage; + +/** + * Tests the conversion between Joda-Time types and String/Date/Calendar. + * + * @author Sjaak Derksen + */ +@WithClasses({ + OversizedKitchenDrawerDto.class, + OversizedKitchenDrawerOptionalDto.class, + RegularKitchenDrawerEntity.class, + VerySpecialNumber.class, + VerySpecialNumberMapper.class, + CutleryInventoryMapper.class, + CutleryInventoryDto.class, + CutleryInventoryEntity.class +}) +@IssueKey("5") +public class LossyConversionTest { + + @ProcessorTest + public void testNoErrorCase() { + + CutleryInventoryDto dto = new CutleryInventoryDto(); + dto.setNumberOfForks( 5 ); + dto.setNumberOfKnifes( (short) 7 ); + dto.setNumberOfSpoons( (byte) 3 ); + dto.setApproximateKnifeLength( 3.7f ); + dto.setDrawerId( 1 ); + + CutleryInventoryEntity entity = CutleryInventoryMapper.INSTANCE.map( dto ); + assertThat( entity.getNumberOfForks() ).isEqualTo( 5L ); + assertThat( entity.getNumberOfKnifes() ).isEqualTo( 7 ); + assertThat( entity.getNumberOfSpoons() ).isEqualTo( (short) 3 ); + assertThat( entity.getApproximateKnifeLength() ).isCloseTo( 3.7d, withinPercentage( 0.0001d ) ); + assertThat( entity.getDrawerId() ).isEqualTo( "1" ); + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerMapper1.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerMapper1.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 24, + message = "Can't map property \"long numberOfForks\". It has a possibly lossy conversion from " + + "long to int.") + }) + public void testConversionFromLongToInt() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper1.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper1.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"OptionalLong numberOfForks\". It has a possibly lossy conversion from " + + "OptionalLong to int.") + }) + public void testConversionFromOptionalLongToInt() { + } + + @ProcessorTest + @WithClasses(KitchenDrawerMapper2.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerMapper2.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 24, + message = "property \"BigInteger numberOfKnifes\" has a possibly lossy conversion " + + "from BigInteger to Integer.") + }) + public void testConversionFromBigIntegerToInteger() { + } + + @ProcessorTest + @WithClasses(KitchenDrawerOptionalMapper2.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerOptionalMapper2.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "property \"Optional numberOfKnifes\" has a possibly lossy conversion " + + "from Optional to Integer.") + }) + public void testConversionFromOptionalBigIntegerToInteger() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerMapper6.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"String drawerId\". It has a possibly lossy conversion from " + + "String to int.") + }) + public void testConversionFromStringToInt() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper6.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"Optional drawerId\". It has a possibly lossy conversion from " + + "Optional to int.") + }) + public void testConversionFromOptionalStringToInt() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerMapper3.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerMapper3.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 24, + message = "Can't map property \"VerySpecialNumber numberOfSpoons\". " + + "It has a possibly lossy conversion from BigInteger to Long.") + }) + public void test2StepConversionFromBigIntegerToLong() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper3.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper3.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"Optional numberOfSpoons\". " + + "It has a possibly lossy conversion from BigInteger to Long.") + }) + public void test2StepConversionFromOptionalBigIntegerToLong() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerMapper4.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerMapper4.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 24, + message = + "Can't map property \"Double depth\". It has a possibly lossy conversion from Double to float.") + }) + public void testConversionFromDoubleToFloat() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper4.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper4.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Can't map property \"Optional depth\". " + + "It has a possibly lossy conversion from Optional to float.") + }) + public void testConversionFromOptionalDoubleToFloat() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerMapper5.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerMapper5.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 24, + message = + "Can't map property \"BigDecimal length\". It has a possibly lossy conversion from BigDecimal to Float.") + }) + public void testConversionFromBigDecimalToFloat() { + } + + @ProcessorTest + @WithClasses(ErroneousKitchenDrawerOptionalMapper5.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousKitchenDrawerOptionalMapper5.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = + "Can't map property \"Optional length\". " + + "It has a possibly lossy conversion from Optional to Float.") + }) + public void testConversionFromOptionalBigDecimalToFloat() { + } + + @ProcessorTest + @WithClasses(KitchenDrawerMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerMapper6.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 24, + message = "property \"double height\" has a possibly lossy conversion from double to float.") + }) + public void test2StepConversionFromDoubleToFloat() { + } + + @ProcessorTest + @WithClasses(KitchenDrawerOptionalMapper6.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = KitchenDrawerOptionalMapper6.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "property \"OptionalDouble height\" has a possibly lossy conversion from " + + "OptionalDouble to float.") + }) + public void test2StepConversionFromOptionalDoubleToFloat() { + } + + @ProcessorTest + @WithClasses(ListMapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = ListMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 22, + message = "collection element has a possibly lossy conversion from BigDecimal to BigInteger.") + }) + public void testListElementConversion() { + } + + @ProcessorTest + @WithClasses(MapMapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = MapMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "map key has a possibly lossy conversion from Long to Integer."), + @Diagnostic(type = MapMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + message = "map value has a possibly lossy conversion from Double to Float.") + }) + public void testMapElementConversion() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/MapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/MapMapper.java new file mode 100644 index 0000000000..547971de43 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/MapMapper.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.ap.test.conversion.lossy; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * + * @author Sjaak Derksen + */ +@Mapper( typeConversionPolicy = ReportingPolicy.WARN ) +public interface MapMapper { + + Map map(Map in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java new file mode 100644 index 0000000000..c42b24cc57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java @@ -0,0 +1,81 @@ +/* + * 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.test.conversion.lossy; + +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * + * @author Sjaak Derksen + */ +public class OversizedKitchenDrawerDto { + + /* yes, its a big drawer */ + private long numberOfForks; + private BigInteger numberOfKnifes; + private VerySpecialNumber numberOfSpoons; + private Double depth; + private BigDecimal length; + private double height; + private String drawerId; + + public long getNumberOfForks() { + return numberOfForks; + } + + public void setNumberOfForks(long numberOfForks) { + this.numberOfForks = numberOfForks; + } + + public BigInteger getNumberOfKnifes() { + return numberOfKnifes; + } + + public void setNumberOfKnifes(BigInteger numberOfKnifes) { + this.numberOfKnifes = numberOfKnifes; + } + + public VerySpecialNumber getNumberOfSpoons() { + return numberOfSpoons; + } + + public void setNumberOfSpoons(VerySpecialNumber numberOfSpoons) { + this.numberOfSpoons = numberOfSpoons; + } + + public Double getDepth() { + return depth; + } + + public void setDepth(Double depth) { + this.depth = depth; + } + + public BigDecimal getLength() { + return length; + } + + public void setLength(BigDecimal length) { + this.length = length; + } + + public double getHeight() { + return height; + } + + public void setHeight(double height) { + this.height = height; + } + + public String getDrawerId() { + return drawerId; + } + + public void setDrawerId(String drawerId) { + this.drawerId = drawerId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java new file mode 100644 index 0000000000..f214fecd1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerOptionalDto.java @@ -0,0 +1,84 @@ +/* + * 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.test.conversion.lossy; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalLong; + +/** + * @author Filip Hrisafov + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class OversizedKitchenDrawerOptionalDto { + + /* yes, its a big drawer */ + private OptionalLong numberOfForks; + private Optional numberOfKnifes; + private Optional numberOfSpoons; + private Optional depth; + private Optional length; + private OptionalDouble height; + private Optional drawerId; + + public OptionalLong getNumberOfForks() { + return numberOfForks; + } + + public void setNumberOfForks(OptionalLong numberOfForks) { + this.numberOfForks = numberOfForks; + } + + public Optional getNumberOfKnifes() { + return numberOfKnifes; + } + + public void setNumberOfKnifes(Optional numberOfKnifes) { + this.numberOfKnifes = numberOfKnifes; + } + + public Optional getNumberOfSpoons() { + return numberOfSpoons; + } + + public void setNumberOfSpoons(Optional numberOfSpoons) { + this.numberOfSpoons = numberOfSpoons; + } + + public Optional getDepth() { + return depth; + } + + public void setDepth(Optional depth) { + this.depth = depth; + } + + public Optional getLength() { + return length; + } + + public void setLength(Optional length) { + this.length = length; + } + + public OptionalDouble getHeight() { + return height; + } + + public void setHeight(OptionalDouble height) { + this.height = height; + } + + public Optional getDrawerId() { + return drawerId; + } + + public void setDrawerId(Optional drawerId) { + this.drawerId = drawerId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java new file mode 100644 index 0000000000..e3ae10bc1f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/RegularKitchenDrawerEntity.java @@ -0,0 +1,77 @@ +/* + * 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.test.conversion.lossy; + +/** + * + * @author Sjaak Derksen + */ +public class RegularKitchenDrawerEntity { + + private int numberOfForks; + private Integer numberOfKnifes; + private Long numberOfSpoons; + private float depth; + private Float length; + private VerySpecialNumber height; + private int drawerId; + + public int getNumberOfForks() { + return numberOfForks; + } + + public void setNumberOfForks(int numberOfForks) { + this.numberOfForks = numberOfForks; + } + + public Integer getNumberOfKnifes() { + return numberOfKnifes; + } + + public void setNumberOfKnifes(Integer numberOfKnifes) { + this.numberOfKnifes = numberOfKnifes; + } + + public Long getNumberOfSpoons() { + return numberOfSpoons; + } + + public void setNumberOfSpoons(Long numberOfSpoons) { + this.numberOfSpoons = numberOfSpoons; + } + + public float getDepth() { + return depth; + } + + public void setDepth(float depth) { + this.depth = depth; + } + + public Float getLength() { + return length; + } + + public void setLength(Float length) { + this.length = length; + } + + public VerySpecialNumber getHeight() { + return height; + } + + public void setHeight(VerySpecialNumber height) { + this.height = height; + } + + public int getDrawerId() { + return drawerId; + } + + public void setDrawerId(int drawerId) { + this.drawerId = drawerId; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumber.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumber.java new file mode 100644 index 0000000000..3f4be13d1f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumber.java @@ -0,0 +1,14 @@ +/* + * 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.test.conversion.lossy; + +/** + * + * @author Sjaak Derksen + */ +public class VerySpecialNumber { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java new file mode 100644 index 0000000000..00b543c728 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.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.ap.test.conversion.lossy; + +import java.math.BigInteger; +import java.util.Optional; + +/** + * @author Sjaak Derksen + */ +public class VerySpecialNumberMapper { + + VerySpecialNumber fromFloat(float f) { + return new VerySpecialNumber(); + } + + BigInteger toBigInteger(VerySpecialNumber v) { + return new BigInteger( "10" ); + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + BigInteger toBigInteger(Optional v) { + return new BigInteger( "10" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanConversionTest.java index cd830faeb9..7c4035417b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanConversionTest.java @@ -1,40 +1,24 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @WithClasses({ BooleanSource.class, BooleanTarget.class, BooleanMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class BooleanConversionTest { - @Test + @ProcessorTest public void shouldApplyBooleanConversion() { BooleanSource source = new BooleanSource(); source.setB( true ); @@ -47,7 +31,7 @@ public void shouldApplyBooleanConversion() { assertThat( target.getBool() ).isEqualTo( Boolean.TRUE ); } - @Test + @ProcessorTest public void shouldApplyReverseBooleanConversion() { BooleanTarget target = new BooleanTarget(); target.setB( Boolean.TRUE ); @@ -60,9 +44,9 @@ public void shouldApplyReverseBooleanConversion() { assertThat( source.getBool() ).isEqualTo( true ); } - @Test + @ProcessorTest @IssueKey( "229" ) - public void wrapperToPrimitveIsNullSafe() { + public void wrapperToPrimitiveIsNullSafe() { BooleanTarget target = new BooleanTarget(); BooleanSource source = BooleanMapper.INSTANCE.targetToSource( target ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanMapper.java index 3e03769199..6765df6ace 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanSource.java index 0b704bb79c..e5b6f0aad0 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanTarget.java index 0b1eb84226..c9a1019c0b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/BooleanTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteSource.java index d75c01c04f..2f647027ed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteTarget.java index 6348be5075..3ec43ebf2f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.java new file mode 100644 index 0000000000..a92b029b14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ByteWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperSource.java index 7aad444696..e7dbe2c06b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperTarget.java index a52054f202..643ec19871 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ByteWrapperTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharConversionTest.java index ff2b03eb7c..def3b45ac5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharConversionTest.java @@ -1,40 +1,24 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @WithClasses({ CharSource.class, CharTarget.class, CharMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class CharConversionTest { - @Test + @ProcessorTest public void shouldApplyCharConversion() { CharSource source = new CharSource(); source.setC( 'G' ); @@ -45,10 +29,10 @@ public void shouldApplyCharConversion() { assertThat( target.getC() ).isEqualTo( Character.valueOf( 'G' ) ); } - @Test + @ProcessorTest public void shouldApplyReverseCharConversion() { CharTarget target = new CharTarget(); - target.setC( Character.valueOf( 'G' ) ); + target.setC( 'G' ); CharSource source = CharMapper.INSTANCE.targetToSource( target ); @@ -56,9 +40,9 @@ public void shouldApplyReverseCharConversion() { assertThat( source.getC() ).isEqualTo( 'G' ); } - @Test + @ProcessorTest @IssueKey( "229" ) - public void wrapperToPrimitveIsNullSafe() { + public void wrapperToPrimitiveIsNullSafe() { CharTarget target = new CharTarget(); CharSource source = CharMapper.INSTANCE.targetToSource( target ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharMapper.java index acf543d879..fabdb78392 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharSource.java index 613750feba..450c2c467c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharTarget.java index 55e669e507..324d43a0ec 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.java new file mode 100644 index 0000000000..e469c44390 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.OptionalDouble; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DoubleOptionalSource { + + private OptionalDouble b = OptionalDouble.empty(); + private OptionalDouble bb = OptionalDouble.empty(); + private OptionalDouble s = OptionalDouble.empty(); + private OptionalDouble ss = OptionalDouble.empty(); + private OptionalDouble i = OptionalDouble.empty(); + private OptionalDouble ii = OptionalDouble.empty(); + private OptionalDouble l = OptionalDouble.empty(); + private OptionalDouble ll = OptionalDouble.empty(); + private OptionalDouble f = OptionalDouble.empty(); + private OptionalDouble ff = OptionalDouble.empty(); + private OptionalDouble d = OptionalDouble.empty(); + private OptionalDouble dd = OptionalDouble.empty(); + + public OptionalDouble getB() { + return b; + } + + public void setB(OptionalDouble b) { + this.b = b; + } + + public OptionalDouble getBb() { + return bb; + } + + public void setBb(OptionalDouble bb) { + this.bb = bb; + } + + public OptionalDouble getS() { + return s; + } + + public void setS(OptionalDouble s) { + this.s = s; + } + + public OptionalDouble getSs() { + return ss; + } + + public void setSs(OptionalDouble ss) { + this.ss = ss; + } + + public OptionalDouble getI() { + return i; + } + + public void setI(OptionalDouble i) { + this.i = i; + } + + public OptionalDouble getIi() { + return ii; + } + + public void setIi(OptionalDouble ii) { + this.ii = ii; + } + + public OptionalDouble getL() { + return l; + } + + public void setL(OptionalDouble l) { + this.l = l; + } + + public OptionalDouble getLl() { + return ll; + } + + public void setLl(OptionalDouble ll) { + this.ll = ll; + } + + public OptionalDouble getF() { + return f; + } + + public void setF(OptionalDouble f) { + this.f = f; + } + + public OptionalDouble getFf() { + return ff; + } + + public void setFf(OptionalDouble ff) { + this.ff = ff; + } + + public OptionalDouble getD() { + return d; + } + + public void setD(OptionalDouble d) { + this.d = d; + } + + public OptionalDouble getDd() { + return dd; + } + + public void setDd(OptionalDouble dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleSource.java index 353d2d08c7..f061bae18b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleTarget.java index 2d62bf69eb..62bcb28716 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.java new file mode 100644 index 0000000000..ecf0d9bc41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class DoubleWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperSource.java index 59d1b7bac1..74c9267db7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperTarget.java index d226d4e2ad..c3584f7083 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/DoubleWrapperTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatSource.java index 5f207d5440..0a3ca13a3d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatTarget.java index 49fae5bca8..f2c7dfe837 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.java new file mode 100644 index 0000000000..f20e6fa5d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class FloatWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperSource.java index e48a581bee..1033aa317e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperTarget.java index 3e2f6d5a28..d9e3deef65 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/FloatWrapperTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.java new file mode 100644 index 0000000000..6543b7bc0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.OptionalInt; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class IntOptionalSource { + + private OptionalInt b = OptionalInt.empty(); + private OptionalInt bb = OptionalInt.empty(); + private OptionalInt s = OptionalInt.empty(); + private OptionalInt ss = OptionalInt.empty(); + private OptionalInt i = OptionalInt.empty(); + private OptionalInt ii = OptionalInt.empty(); + private OptionalInt l = OptionalInt.empty(); + private OptionalInt ll = OptionalInt.empty(); + private OptionalInt f = OptionalInt.empty(); + private OptionalInt ff = OptionalInt.empty(); + private OptionalInt d = OptionalInt.empty(); + private OptionalInt dd = OptionalInt.empty(); + + public OptionalInt getB() { + return b; + } + + public void setB(OptionalInt b) { + this.b = b; + } + + public OptionalInt getBb() { + return bb; + } + + public void setBb(OptionalInt bb) { + this.bb = bb; + } + + public OptionalInt getS() { + return s; + } + + public void setS(OptionalInt s) { + this.s = s; + } + + public OptionalInt getSs() { + return ss; + } + + public void setSs(OptionalInt ss) { + this.ss = ss; + } + + public OptionalInt getI() { + return i; + } + + public void setI(OptionalInt i) { + this.i = i; + } + + public OptionalInt getIi() { + return ii; + } + + public void setIi(OptionalInt ii) { + this.ii = ii; + } + + public OptionalInt getL() { + return l; + } + + public void setL(OptionalInt l) { + this.l = l; + } + + public OptionalInt getLl() { + return ll; + } + + public void setLl(OptionalInt ll) { + this.ll = ll; + } + + public OptionalInt getF() { + return f; + } + + public void setF(OptionalInt f) { + this.f = f; + } + + public OptionalInt getFf() { + return ff; + } + + public void setFf(OptionalInt ff) { + this.ff = ff; + } + + public OptionalInt getD() { + return d; + } + + public void setD(OptionalInt d) { + this.d = d; + } + + public OptionalInt getDd() { + return dd; + } + + public void setDd(OptionalInt dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntSource.java index f8b1eb0023..de2143fe6d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntTarget.java index 4c483c67bd..1e0c4a558d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.java new file mode 100644 index 0000000000..b1243d9c1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class IntWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperSource.java index 36b7ece8ab..8912a8f592 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperTarget.java index 73ef924c0a..3b8d6a0044 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/IntWrapperTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.java new file mode 100644 index 0000000000..1df7baaec9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.OptionalLong; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class LongOptionalSource { + + private OptionalLong b = OptionalLong.empty(); + private OptionalLong bb = OptionalLong.empty(); + private OptionalLong s = OptionalLong.empty(); + private OptionalLong ss = OptionalLong.empty(); + private OptionalLong i = OptionalLong.empty(); + private OptionalLong ii = OptionalLong.empty(); + private OptionalLong l = OptionalLong.empty(); + private OptionalLong ll = OptionalLong.empty(); + private OptionalLong f = OptionalLong.empty(); + private OptionalLong ff = OptionalLong.empty(); + private OptionalLong d = OptionalLong.empty(); + private OptionalLong dd = OptionalLong.empty(); + + public OptionalLong getB() { + return b; + } + + public void setB(OptionalLong b) { + this.b = b; + } + + public OptionalLong getBb() { + return bb; + } + + public void setBb(OptionalLong bb) { + this.bb = bb; + } + + public OptionalLong getS() { + return s; + } + + public void setS(OptionalLong s) { + this.s = s; + } + + public OptionalLong getSs() { + return ss; + } + + public void setSs(OptionalLong ss) { + this.ss = ss; + } + + public OptionalLong getI() { + return i; + } + + public void setI(OptionalLong i) { + this.i = i; + } + + public OptionalLong getIi() { + return ii; + } + + public void setIi(OptionalLong ii) { + this.ii = ii; + } + + public OptionalLong getL() { + return l; + } + + public void setL(OptionalLong l) { + this.l = l; + } + + public OptionalLong getLl() { + return ll; + } + + public void setLl(OptionalLong ll) { + this.ll = ll; + } + + public OptionalLong getF() { + return f; + } + + public void setF(OptionalLong f) { + this.f = f; + } + + public OptionalLong getFf() { + return ff; + } + + public void setFf(OptionalLong ff) { + this.ff = ff; + } + + public OptionalLong getD() { + return d; + } + + public void setD(OptionalLong d) { + this.d = d; + } + + public OptionalLong getDd() { + return dd; + } + + public void setDd(OptionalLong dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongSource.java index 7991424a03..710bc58172 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongTarget.java index c3365dfb68..7d1f35b23d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.java new file mode 100644 index 0000000000..bed8ab6b59 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class LongWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperSource.java index 51dd878df3..9c876f7c1e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperTarget.java index 9328a969e5..8a6218fa5b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/LongWrapperTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberConversionTest.java index 134bc20403..61be21e797 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberConversionTest.java @@ -1,30 +1,15 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @WithClasses({ ByteSource.class, @@ -53,10 +38,9 @@ DoubleWrapperTarget.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class NumberConversionTest { - @Test + @ProcessorTest public void shouldApplyByteConversions() { ByteSource source = new ByteSource(); source.setB( (byte) 1 ); @@ -89,7 +73,7 @@ public void shouldApplyByteConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyByteWrapperConversions() { ByteWrapperSource source = new ByteWrapperSource(); source.setB( (byte) 1 ); @@ -122,7 +106,7 @@ public void shouldApplyByteWrapperConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyShortConversions() { ShortSource source = new ShortSource(); source.setB( (short) 1 ); @@ -155,7 +139,7 @@ public void shouldApplyShortConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyShortWrapperConversions() { ShortWrapperSource source = new ShortWrapperSource(); source.setB( (short) 1 ); @@ -188,7 +172,7 @@ public void shouldApplyShortWrapperConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyIntConversions() { IntSource source = new IntSource(); source.setB( 1 ); @@ -221,7 +205,7 @@ public void shouldApplyIntConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyIntWrapperConversions() { IntWrapperSource source = new IntWrapperSource(); source.setB( 1 ); @@ -254,7 +238,7 @@ public void shouldApplyIntWrapperConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyLongConversions() { LongSource source = new LongSource(); source.setB( 1 ); @@ -287,7 +271,7 @@ public void shouldApplyLongConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyLongWrapperConversions() { LongWrapperSource source = new LongWrapperSource(); source.setB( (long) 1 ); @@ -320,7 +304,7 @@ public void shouldApplyLongWrapperConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyFloatConversions() { FloatSource source = new FloatSource(); source.setB( 1 ); @@ -353,7 +337,7 @@ public void shouldApplyFloatConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyFloatWrapperConversions() { FloatWrapperSource source = new FloatWrapperSource(); source.setB( 1f ); @@ -386,7 +370,7 @@ public void shouldApplyFloatWrapperConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyDoubleConversions() { DoubleSource source = new DoubleSource(); source.setB( 1 ); @@ -419,7 +403,7 @@ public void shouldApplyDoubleConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest public void shouldApplyDoubleWrapperConversions() { DoubleWrapperSource source = new DoubleWrapperSource(); source.setB( 1d ); @@ -452,9 +436,9 @@ public void shouldApplyDoubleWrapperConversions() { assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); } - @Test + @ProcessorTest @IssueKey( "229" ) - public void wrapperToPrimitveIsNullSafe() { + public void wrapperToPrimitiveIsNullSafe() { assertThat( SourceTargetMapper.INSTANCE.sourceToTarget( new ByteWrapperSource() ) ).isNotNull(); assertThat( SourceTargetMapper.INSTANCE.sourceToTarget( new DoubleWrapperSource() ) ).isNotNull(); assertThat( SourceTargetMapper.INSTANCE.sourceToTarget( new ShortWrapperSource() ) ).isNotNull(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java new file mode 100644 index 0000000000..0857b20473 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberOptionalConversionTest.java @@ -0,0 +1,357 @@ +/* + * 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.test.conversion.nativetypes; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + ByteTarget.class, + ByteWrapperOptionalSource.class, + ByteWrapperTarget.class, + ShortTarget.class, + ShortWrapperOptionalSource.class, + ShortWrapperTarget.class, + IntOptionalSource.class, + IntTarget.class, + IntWrapperOptionalSource.class, + IntWrapperTarget.class, + LongOptionalSource.class, + LongTarget.class, + LongWrapperOptionalSource.class, + LongWrapperTarget.class, + FloatWrapperOptionalSource.class, + FloatWrapperTarget.class, + DoubleOptionalSource.class, + DoubleTarget.class, + DoubleWrapperOptionalSource.class, + DoubleWrapperTarget.class, + OptionalNumberConversionMapper.class +}) +public class NumberOptionalConversionTest { + + @ProcessorTest + public void shouldApplyByteWrapperConversions() { + ByteWrapperOptionalSource source = new ByteWrapperOptionalSource(); + source.setB( Optional.of( (byte) 1 ) ); + source.setBb( Optional.of( (byte) 2 ) ); + source.setS( Optional.of( (byte) 3 ) ); + source.setSs( Optional.of( (byte) 4 ) ); + source.setI( Optional.of( (byte) 5 ) ); + source.setIi( Optional.of( (byte) 6 ) ); + source.setL( Optional.of( (byte) 7 ) ); + source.setLl( Optional.of( (byte) 8 ) ); + source.setF( Optional.of( (byte) 9 ) ); + source.setFf( Optional.of( (byte) 10 ) ); + source.setD( Optional.of( (byte) 11 ) ); + source.setDd( Optional.of( (byte) 12 ) ); + + ByteWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyShortWrapperConversions() { + ShortWrapperOptionalSource source = new ShortWrapperOptionalSource(); + source.setB( Optional.of( (short) 1 ) ); + source.setBb( Optional.of( (short) 2 ) ); + source.setS( Optional.of( (short) 3 ) ); + source.setSs( Optional.of( (short) 4 ) ); + source.setI( Optional.of( (short) 5 ) ); + source.setIi( Optional.of( (short) 6 ) ); + source.setL( Optional.of( (short) 7 ) ); + source.setLl( Optional.of( (short) 8 ) ); + source.setF( Optional.of( (short) 9 ) ); + source.setFf( Optional.of( (short) 10 ) ); + source.setD( Optional.of( (short) 11 ) ); + source.setDd( Optional.of( (short) 12 ) ); + + ShortWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyIntConversions() { + IntOptionalSource source = new IntOptionalSource(); + source.setB( OptionalInt.of( 1 ) ); + source.setBb( OptionalInt.of( 2 ) ); + source.setS( OptionalInt.of( 3 ) ); + source.setSs( OptionalInt.of( 4 ) ); + source.setI( OptionalInt.of( 5 ) ); + source.setIi( OptionalInt.of( 6 ) ); + source.setL( OptionalInt.of( 7 ) ); + source.setLl( OptionalInt.of( 8 ) ); + source.setF( OptionalInt.of( 9 ) ); + source.setFf( OptionalInt.of( 10 ) ); + source.setD( OptionalInt.of( 11 ) ); + source.setDd( OptionalInt.of( 12 ) ); + + IntTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyIntWrapperConversions() { + IntWrapperOptionalSource source = new IntWrapperOptionalSource(); + source.setB( Optional.of( 1 ) ); + source.setBb( Optional.of( 2 ) ); + source.setS( Optional.of( 3 ) ); + source.setSs( Optional.of( 4 ) ); + source.setI( Optional.of( 5 ) ); + source.setIi( Optional.of( 6 ) ); + source.setL( Optional.of( 7 ) ); + source.setLl( Optional.of( 8 ) ); + source.setF( Optional.of( 9 ) ); + source.setFf( Optional.of( 10 ) ); + source.setD( Optional.of( 11 ) ); + source.setDd( Optional.of( 12 ) ); + + IntWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyLongConversions() { + LongOptionalSource source = new LongOptionalSource(); + source.setB( OptionalLong.of( 1 ) ); + source.setBb( OptionalLong.of( 2 ) ); + source.setS( OptionalLong.of( 3 ) ); + source.setSs( OptionalLong.of( 4 ) ); + source.setI( OptionalLong.of( 5 ) ); + source.setIi( OptionalLong.of( 6 ) ); + source.setL( OptionalLong.of( 7 ) ); + source.setLl( OptionalLong.of( 8 ) ); + source.setF( OptionalLong.of( 9 ) ); + source.setFf( OptionalLong.of( 10 ) ); + source.setD( OptionalLong.of( 11 ) ); + source.setDd( OptionalLong.of( 12 ) ); + + LongTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyLongWrapperConversions() { + LongWrapperOptionalSource source = new LongWrapperOptionalSource(); + source.setB( Optional.of( (long) 1 ) ); + source.setBb( Optional.of( (long) 2 ) ); + source.setS( Optional.of( (long) 3 ) ); + source.setSs( Optional.of( (long) 4 ) ); + source.setI( Optional.of( (long) 5 ) ); + source.setIi( Optional.of( (long) 6 ) ); + source.setL( Optional.of( (long) 7 ) ); + source.setLl( Optional.of( (long) 8 ) ); + source.setF( Optional.of( (long) 9 ) ); + source.setFf( Optional.of( (long) 10 ) ); + source.setD( Optional.of( (long) 11 ) ); + source.setDd( Optional.of( (long) 12 ) ); + + LongWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyFloatWrapperConversions() { + FloatWrapperOptionalSource source = new FloatWrapperOptionalSource(); + source.setB( Optional.of( 1f ) ); + source.setBb( Optional.of( 2f ) ); + source.setS( Optional.of( 3f ) ); + source.setSs( Optional.of( 4f ) ); + source.setI( Optional.of( 5f ) ); + source.setIi( Optional.of( 6f ) ); + source.setL( Optional.of( 7f ) ); + source.setLl( Optional.of( 8f ) ); + source.setF( Optional.of( 9f ) ); + source.setFf( Optional.of( 10f ) ); + source.setD( Optional.of( 11f ) ); + source.setDd( Optional.of( 12f ) ); + + FloatWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyDoubleConversions() { + DoubleOptionalSource source = new DoubleOptionalSource(); + source.setB( OptionalDouble.of( 1 ) ); + source.setBb( OptionalDouble.of( 2 ) ); + source.setS( OptionalDouble.of( 3 ) ); + source.setSs( OptionalDouble.of( 4 ) ); + source.setI( OptionalDouble.of( 5 ) ); + source.setIi( OptionalDouble.of( 6 ) ); + source.setL( OptionalDouble.of( 7 ) ); + source.setLl( OptionalDouble.of( 8 ) ); + source.setF( OptionalDouble.of( 9 ) ); + source.setFf( OptionalDouble.of( 10 ) ); + source.setD( OptionalDouble.of( 11 ) ); + source.setDd( OptionalDouble.of( 12 ) ); + + DoubleTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + public void shouldApplyDoubleWrapperConversions() { + DoubleWrapperOptionalSource source = new DoubleWrapperOptionalSource(); + source.setB( Optional.of( 1d ) ); + source.setBb( Optional.of( 2d ) ); + source.setS( Optional.of( 3d ) ); + source.setSs( Optional.of( 4d ) ); + source.setI( Optional.of( 5d ) ); + source.setIi( Optional.of( 6d ) ); + source.setL( Optional.of( 7d ) ); + source.setLl( Optional.of( 8d ) ); + source.setF( Optional.of( 9d ) ); + source.setFf( Optional.of( 10d ) ); + source.setD( Optional.of( 11d ) ); + source.setDd( Optional.of( 12d ) ); + + DoubleWrapperTarget target = OptionalNumberConversionMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( (byte) 1 ); + assertThat( target.getBb() ).isEqualTo( Byte.valueOf( (byte) 2 ) ); + assertThat( target.getS() ).isEqualTo( (short) 3 ); + assertThat( target.getSs() ).isEqualTo( Short.valueOf( (short) 4 ) ); + assertThat( target.getI() ).isEqualTo( 5 ); + assertThat( target.getIi() ).isEqualTo( Integer.valueOf( 6 ) ); + assertThat( target.getL() ).isEqualTo( 7 ); + assertThat( target.getLl() ).isEqualTo( Long.valueOf( 8 ) ); + assertThat( target.getF() ).isEqualTo( 9f ); + assertThat( target.getFf() ).isEqualTo( Float.valueOf( 10f ) ); + assertThat( target.getD() ).isEqualTo( 11d ); + assertThat( target.getDd() ).isEqualTo( Double.valueOf( 12d ) ); + } + + @ProcessorTest + @IssueKey("229") + public void wrapperToPrimitiveIsNullSafe() { + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new ByteWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new DoubleWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new ShortWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new IntWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new FloatWrapperOptionalSource() ) ) + .isNotNull(); + assertThat( OptionalNumberConversionMapper.INSTANCE.sourceToTarget( new LongWrapperOptionalSource() ) ) + .isNotNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.java new file mode 100644 index 0000000000..94472586fa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/OptionalNumberConversionMapper.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.ap.test.conversion.nativetypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalNumberConversionMapper { + + OptionalNumberConversionMapper INSTANCE = Mappers.getMapper( OptionalNumberConversionMapper.class ); + + ByteWrapperTarget sourceToTarget(ByteWrapperOptionalSource source); + + ByteWrapperOptionalSource targetToSource(ByteWrapperTarget target); + + ShortWrapperTarget sourceToTarget(ShortWrapperOptionalSource source); + + ShortWrapperOptionalSource targetToSource(ShortWrapperTarget target); + + IntTarget sourceToTarget(IntOptionalSource source); + + IntOptionalSource targetToSource(IntTarget target); + + IntWrapperTarget sourceToTarget(IntWrapperOptionalSource source); + + IntWrapperOptionalSource targetToSource(IntWrapperTarget target); + + LongTarget sourceToTarget(LongOptionalSource source); + + LongOptionalSource targetToSource(LongTarget target); + + LongWrapperTarget sourceToTarget(LongWrapperOptionalSource source); + + LongWrapperOptionalSource targetToSource(LongWrapperTarget target); + + FloatWrapperTarget sourceToTarget(FloatWrapperOptionalSource source); + + FloatWrapperOptionalSource targetToSource(FloatWrapperTarget target); + + DoubleTarget sourceToTarget(DoubleOptionalSource source); + + DoubleOptionalSource targetToSource(DoubleTarget target); + + DoubleWrapperTarget sourceToTarget(DoubleWrapperOptionalSource source); + + DoubleWrapperOptionalSource targetToSource(DoubleWrapperTarget target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortSource.java index a89d30b0c1..cf88274634 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortTarget.java index ee14ac2174..bd5635d07a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.java new file mode 100644 index 0000000000..16acf6c69b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperOptionalSource.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.ap.test.conversion.nativetypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class ShortWrapperOptionalSource { + + private Optional b = Optional.empty(); + private Optional bb = Optional.empty(); + private Optional s = Optional.empty(); + private Optional ss = Optional.empty(); + private Optional i = Optional.empty(); + private Optional ii = Optional.empty(); + private Optional l = Optional.empty(); + private Optional ll = Optional.empty(); + private Optional f = Optional.empty(); + private Optional ff = Optional.empty(); + private Optional d = Optional.empty(); + private Optional dd = Optional.empty(); + + public Optional getB() { + return b; + } + + public void setB(Optional b) { + this.b = b; + } + + public Optional getBb() { + return bb; + } + + public void setBb(Optional bb) { + this.bb = bb; + } + + public Optional getS() { + return s; + } + + public void setS(Optional s) { + this.s = s; + } + + public Optional getSs() { + return ss; + } + + public void setSs(Optional ss) { + this.ss = ss; + } + + public Optional getI() { + return i; + } + + public void setI(Optional i) { + this.i = i; + } + + public Optional getIi() { + return ii; + } + + public void setIi(Optional ii) { + this.ii = ii; + } + + public Optional getL() { + return l; + } + + public void setL(Optional l) { + this.l = l; + } + + public Optional getLl() { + return ll; + } + + public void setLl(Optional ll) { + this.ll = ll; + } + + public Optional getF() { + return f; + } + + public void setF(Optional f) { + this.f = f; + } + + public Optional getFf() { + return ff; + } + + public void setFf(Optional ff) { + this.ff = ff; + } + + public Optional getD() { + return d; + } + + public void setD(Optional d) { + this.d = d; + } + + public Optional getDd() { + return dd; + } + + public void setDd(Optional dd) { + this.dd = dd; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperSource.java index dc9a6d208b..9a52592df9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperSource.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperSource.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperTarget.java index 0d2b9c9879..4093f3988c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/ShortWrapperTarget.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/SourceTargetMapper.java index 276a9919e1..8e4f51b9c7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.nativetypes; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java new file mode 100644 index 0000000000..39b4e98319 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java @@ -0,0 +1,258 @@ +/* + * 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.test.conversion.numbers; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultLocale; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses({ + Source.class, + Target.class, + SourceTargetMapper.class +}) +@DefaultLocale("en") +public class NumberFormatConversionTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( SourceTargetMapper.class ); + + @ProcessorTest + public void shouldApplyStringConversions() { + Source source = new Source(); + source.setI( 1 ); + source.setIi( 2 ); + source.setD( 3.0 ); + source.setDd( 4.0 ); + source.setF( 3.0f ); + source.setFf( 4.0f ); + source.setL( 5L ); + source.setLl( 6L ); + source.setB( (byte) 7 ); + source.setBb( (byte) 8 ); + + source.setComplex1( 345346.456756 ); + source.setComplex2( 5007034.3 ); + + source.setBigDecimal1( new BigDecimal( "987E-20" ) ); + source.setBigInteger1( new BigInteger( "1234567890000" ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getI() ).isEqualTo( "1.00" ); + assertThat( target.getIi() ).isEqualTo( "2.00" ); + assertThat( target.getD() ).isEqualTo( "3.00" ); + assertThat( target.getDd() ).isEqualTo( "4.00" ); + assertThat( target.getF() ).isEqualTo( "3.00" ); + assertThat( target.getFf() ).isEqualTo( "4.00" ); + assertThat( target.getL() ).isEqualTo( "5.00" ); + assertThat( target.getLl() ).isEqualTo( "6.00" ); + assertThat( target.getB() ).isEqualTo( "7.00" ); + assertThat( target.getBb() ).isEqualTo( "8.00" ); + + assertThat( target.getComplex1() ).isEqualTo( "345.35E3" ); + assertThat( target.getComplex2() ).isEqualTo( "$5007034.30" ); + + assertThat( target.getBigDecimal1() ).isEqualTo( "9.87E-18" ); + assertThat( target.getBigInteger1() ).isEqualTo( "1.23456789E12" ); + } + + @ProcessorTest + public void shouldApplyStringConversionsWithCustomLocale() { + Source source = new Source(); + source.setI( 1 ); + source.setIi( 2 ); + source.setD( 3.0 ); + source.setDd( 4.0 ); + source.setF( 3.0f ); + source.setFf( 4.0f ); + source.setL( 5L ); + source.setLl( 6L ); + source.setB( (byte) 7 ); + source.setBb( (byte) 8 ); + + source.setComplex1( 345346.456756 ); + source.setComplex2( 5007034.3 ); + + source.setBigDecimal1( new BigDecimal( "987E-20" ) ); + source.setBigInteger1( new BigInteger( "1234567890000" ) ); + + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getI() ).isEqualTo( "1.00" ); + assertThat( target.getIi() ).isEqualTo( "2.00" ); + assertThat( target.getD() ).isEqualTo( "3.00" ); + assertThat( target.getDd() ).isEqualTo( "4.00" ); + assertThat( target.getF() ).isEqualTo( "3.00" ); + assertThat( target.getFf() ).isEqualTo( "4.00" ); + assertThat( target.getL() ).isEqualTo( "5.00" ); + assertThat( target.getLl() ).isEqualTo( "6.00" ); + assertThat( target.getB() ).isEqualTo( "7.00" ); + assertThat( target.getBb() ).isEqualTo( "8.00" ); + + assertThat( target.getComplex1() ).isEqualTo( "345.35E3" ); + assertThat( target.getComplex2() ).isEqualTo( "$5007034.30" ); + + assertThat( target.getBigDecimal1() ).isEqualTo( "9,87E-18" ); + assertThat( target.getBigInteger1() ).isEqualTo( "1,23456789E12" ); + } + + @ProcessorTest + public void shouldApplyReverseStringConversions() { + Target target = new Target(); + target.setI( "1.00" ); + target.setIi( "2.00" ); + target.setD( "3.00" ); + target.setDd( "4.00" ); + target.setF( "3.00" ); + target.setFf( "4.00" ); + target.setL( "5.00" ); + target.setLl( "6.00" ); + target.setB( "7.00" ); + target.setBb( "8.00" ); + + target.setComplex1( "345.35E3" ); + target.setComplex2( "$5007034.30" ); + + target.setBigDecimal1( "9.87E-18" ); + target.setBigInteger1( "1.23456789E12" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getI() ).isEqualTo( 1 ); + assertThat( source.getIi() ).isEqualTo( Integer.valueOf( 2 ) ); + assertThat( source.getD() ).isEqualTo( 3.0 ); + assertThat( source.getDd() ).isEqualTo( Double.valueOf( 4.0 ) ); + assertThat( source.getF() ).isEqualTo( 3.0f ); + assertThat( source.getFf() ).isEqualTo( Float.valueOf( 4.0f ) ); + assertThat( source.getL() ).isEqualTo( 5L ); + assertThat( source.getLl() ).isEqualTo( Long.valueOf( 6L ) ); + assertThat( source.getB() ).isEqualTo( (byte) 7 ); + assertThat( source.getBb() ).isEqualTo( (byte) 8 ); + + assertThat( source.getComplex1() ).isEqualTo( 345350.0 ); + assertThat( source.getComplex2() ).isEqualTo( 5007034.3 ); + + assertThat( source.getBigDecimal1() ).isEqualTo( new BigDecimal( "987E-20" ) ); + assertThat( source.getBigInteger1() ).isEqualTo( new BigInteger( "1234567890000" ) ); + } + + @ProcessorTest + public void shouldApplyReverseStringConversionsWithCustomLocale() { + Target target = new Target(); + target.setI( "1.00" ); + target.setIi( "2.00" ); + target.setD( "3.00" ); + target.setDd( "4.00" ); + target.setF( "3.00" ); + target.setFf( "4.00" ); + target.setL( "5.00" ); + target.setLl( "6.00" ); + target.setB( "7.00" ); + target.setBb( "8.00" ); + + target.setComplex1( "345.35E3" ); + target.setComplex2( "$5007034.30" ); + + target.setBigDecimal1( "9,87E-18" ); + target.setBigInteger1( "1,23456789E12" ); + + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getI() ).isEqualTo( 1 ); + assertThat( source.getIi() ).isEqualTo( Integer.valueOf( 2 ) ); + assertThat( source.getD() ).isEqualTo( 3.0 ); + assertThat( source.getDd() ).isEqualTo( Double.valueOf( 4.0 ) ); + assertThat( source.getF() ).isEqualTo( 3.0f ); + assertThat( source.getFf() ).isEqualTo( Float.valueOf( 4.0f ) ); + assertThat( source.getL() ).isEqualTo( 5L ); + assertThat( source.getLl() ).isEqualTo( Long.valueOf( 6L ) ); + assertThat( source.getB() ).isEqualTo( (byte) 7 ); + assertThat( source.getBb() ).isEqualTo( (byte) 8 ); + + assertThat( source.getComplex1() ).isEqualTo( 345350.0 ); + assertThat( source.getComplex2() ).isEqualTo( 5007034.3 ); + + assertThat( source.getBigDecimal1() ).isEqualTo( new BigDecimal( "987E-20" ) ); + assertThat( source.getBigInteger1() ).isEqualTo( new BigInteger( "1234567890000" ) ); + } + + @ProcessorTest + public void shouldApplyStringConversionsToIterables() { + + List target = SourceTargetMapper.INSTANCE.sourceToTarget( List.of( 2f ) ); + + assertThat( target ).hasSize( 1 ); + assertThat( target ).isEqualTo( List.of( "2.00" ) ); + + List source = SourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source ).hasSize( 1 ); + assertThat( source ).isEqualTo( List.of( 2.00f ) ); + } + + @ProcessorTest + public void shouldApplyStringConversionsToIterablesWithCustomLocale() { + + List target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( + List.of( new BigDecimal("987E-20") ) + ); + + assertThat( target ).hasSize( 1 ); + assertThat( target ).isEqualTo( List.of( "9,87E-18" ) ); + + List source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + assertThat( source ).hasSize( 1 ); + assertThat( source ).isEqualTo( List.of( new BigDecimal("987E-20") ) ); + } + + @ProcessorTest + public void shouldApplyStringConversionsToMaps() { + + Map source1 = new HashMap<>(); + source1.put( 1.0001f, 2.01f ); + + Map target = SourceTargetMapper.INSTANCE.sourceToTarget( source1 ); + assertThat( target ).hasSize( 1 ); + assertThat( target ).contains( entry( "1.00", "2" ) ); + + Map source2 = SourceTargetMapper.INSTANCE.targetToSource( target ); + assertThat( source2 ).hasSize( 1 ); + assertThat( source2 ).contains( entry( 1.00f, 2f ) ); + + } + + @ProcessorTest + public void shouldApplyStringConversionsToMapsWithCustomLocale() { + + Map source1 = new HashMap<>(); + source1.put( new BigDecimal( "987E-20" ), new BigDecimal( "97E-10" ) ); + + Map target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source1 ); + assertThat( target ).hasSize( 1 ); + assertThat( target ).contains( entry( "9,87E-18", "9,7E-9" ) ); + + Map source2 = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( target ); + assertThat( source2 ).hasSize( 1 ); + assertThat( source2 ).contains( entry( new BigDecimal( "987E-20" ), new BigDecimal( "97E-10" ) ) ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/Source.java new file mode 100644 index 0000000000..96f9a704b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/Source.java @@ -0,0 +1,141 @@ +/* + * 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.test.conversion.numbers; + +import java.math.BigDecimal; +import java.math.BigInteger; + +public class Source { + + private int i; + private Integer ii; + private double d; + private Double dd; + private float f; + private Float ff; + private long l; + private Long ll; + private byte b; + private Byte bb; + + private double complex1; + private double complex2; + + private BigDecimal bigDecimal1; + private BigInteger bigInteger1; + + public int getI() { + return i; + } + + public void setI(int i) { + this.i = i; + } + + public Integer getIi() { + return ii; + } + + public void setIi(Integer ii) { + this.ii = ii; + } + + public double getD() { + return d; + } + + public void setD(double d) { + this.d = d; + } + + public Double getDd() { + return dd; + } + + public void setDd(Double dd) { + this.dd = dd; + } + + public float getF() { + return f; + } + + public void setF(float f) { + this.f = f; + } + + public Float getFf() { + return ff; + } + + public void setFf(Float ff) { + this.ff = ff; + } + + public long getL() { + return l; + } + + public void setL(long l) { + this.l = l; + } + + public Long getLl() { + return ll; + } + + public void setLl(Long ll) { + this.ll = ll; + } + + public byte getB() { + return b; + } + + public void setB(byte b) { + this.b = b; + } + + public Byte getBb() { + return bb; + } + + public void setBb(Byte bb) { + this.bb = bb; + } + + public double getComplex1() { + return complex1; + } + + public void setComplex1(double complex1) { + this.complex1 = complex1; + } + + public double getComplex2() { + return complex2; + } + + public void setComplex2(double complex2) { + this.complex2 = complex2; + } + + public BigDecimal getBigDecimal1() { + return bigDecimal1; + } + + public void setBigDecimal1(BigDecimal bigDecimal1) { + this.bigDecimal1 = bigDecimal1; + } + + public BigInteger getBigInteger1() { + return bigInteger1; + } + + public void setBigInteger1(BigInteger bigInteger1) { + this.bigInteger1 = bigInteger1; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java new file mode 100644 index 0000000000..70b16970c6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.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.ap.test.conversion.numbers; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; + +import org.mapstruct.InheritInverseConfiguration; +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper { + + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + String NUMBER_FORMAT = "##.00"; + + @Mappings( { + @Mapping( target = "i", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "ii", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "d", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "dd", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "f", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "ff", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "l", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "ll", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "b", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "bb", numberFormat = NUMBER_FORMAT ), + @Mapping( target = "complex1", numberFormat = "##0.##E0" ), + @Mapping( target = "complex2", numberFormat = "$#.00" ), + @Mapping( target = "bigDecimal1", numberFormat = "#0.#E0" ), + @Mapping( target = "bigInteger1", numberFormat = "0.#############E0" ) + + } ) + Target sourceToTarget(Source source); + + @Mappings( { + @Mapping( target = "i", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ii", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "d", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "dd", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "f", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ff", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "l", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "ll", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "b", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "bb", numberFormat = NUMBER_FORMAT, locale = "ru" ), + @Mapping( target = "complex1", numberFormat = "##0.##E0", locale = "ru" ), + @Mapping( target = "complex2", numberFormat = "$#.00", locale = "ru" ), + @Mapping( target = "bigDecimal1", numberFormat = "#0.#E0", locale = "ru" ), + @Mapping( target = "bigInteger1", numberFormat = "0.#############E0", locale = "ru" ) + + } ) + Target sourceToTargetWithCustomLocale(Source source); + + @InheritInverseConfiguration( name = "sourceToTarget" ) + Source targetToSource(Target target); + + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + Source targetToSourceWithCustomLocale(Target target); + + @IterableMapping( numberFormat = NUMBER_FORMAT ) + List sourceToTarget(List source); + + @InheritInverseConfiguration( name = "sourceToTarget" ) + List targetToSource(List source); + + @IterableMapping( numberFormat = "#0.#E0", locale = "fr" ) + List sourceToTargetWithCustomLocale(List source); + + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + List targetToSourceWithCustomLocale(List source); + + @MapMapping( keyNumberFormat = NUMBER_FORMAT, valueNumberFormat = "##" ) + Map sourceToTarget(Map source); + + @MapMapping( keyNumberFormat = "#0.#E0", valueNumberFormat = "0.#############E0", locale = "fr" ) + Map sourceToTargetWithCustomLocale(Map source); + + @InheritInverseConfiguration( name = "sourceToTarget" ) + Map targetToSource(Map source); + + @InheritInverseConfiguration( name = "sourceToTargetWithCustomLocale" ) + Map targetToSourceWithCustomLocale(Map source); + +} + + diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/Target.java new file mode 100644 index 0000000000..948dcb15b7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/Target.java @@ -0,0 +1,138 @@ +/* + * 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.test.conversion.numbers; + +public class Target { + + private String i; + private String ii; + private String d; + private String dd; + private String f; + private String ff; + private String l; + private String ll; + private String b; + private String bb; + + private String complex1; + private String complex2; + + private String bigDecimal1; + private String bigInteger1; + + public String getI() { + return i; + } + + public void setI(String i) { + this.i = i; + } + + public String getIi() { + return ii; + } + + public void setIi(String ii) { + this.ii = ii; + } + + public String getD() { + return d; + } + + public void setD(String d) { + this.d = d; + } + + public String getDd() { + return dd; + } + + public void setDd(String dd) { + this.dd = dd; + } + + public String getF() { + return f; + } + + public void setF(String f) { + this.f = f; + } + + public String getFf() { + return ff; + } + + public void setFf(String ff) { + this.ff = ff; + } + + public String getL() { + return l; + } + + public void setL(String l) { + this.l = l; + } + + public String getLl() { + return ll; + } + + public void setLl(String ll) { + this.ll = ll; + } + + public String getB() { + return b; + } + + public void setB(String b) { + this.b = b; + } + + public String getBb() { + return bb; + } + + public void setBb(String bb) { + this.bb = bb; + } + + public String getComplex1() { + return complex1; + } + + public void setComplex1(String complex1) { + this.complex1 = complex1; + } + + public String getComplex2() { + return complex2; + } + + public void setComplex2(String complex2) { + this.complex2 = complex2; + } + + public String getBigDecimal1() { + return bigDecimal1; + } + + public void setBigDecimal1(String bigDecimal1) { + this.bigDecimal1 = bigDecimal1; + } + + public String getBigInteger1() { + return bigInteger1; + } + + public void setBigInteger1(String bigInteger1) { + this.bigInteger1 = bigInteger1; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/ConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/ConversionTest.java index 68a548fa88..42c72e0727 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/ConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/ConversionTest.java @@ -1,35 +1,19 @@ -/** - * Copyright 2012-2015 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.conversion.precedence; -import static org.fest.assertions.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; @WithClasses({ Source.class, Target.class, SourceTargetMapper.class, IntegerStringMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ConversionTest { - @Test + @ProcessorTest public void shouldInvokeMappingMethodInsteadOfConversion() { Source source = new Source(); source.setFoo( 42 ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/IntegerStringMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/IntegerStringMapper.java index e97d5913b2..b56498ef0d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/IntegerStringMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/IntegerStringMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.precedence; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/Source.java index f17c189a27..85cc46cb5f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.precedence; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/SourceTargetMapper.java index 588d778456..e618c13154 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.precedence; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/Target.java index 47fd964d8f..33d3ef2044 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.precedence; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Source.java index 810efd6d43..7f95abc797 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Source.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.string; @@ -36,6 +23,7 @@ public class Source { private Boolean boolBool; private char c; private Character cc; + private StringBuilder sb; private Object object; public byte getB() { @@ -173,4 +161,12 @@ public Object getObject() { public void setObject(Object object) { this.object = object; } + + public StringBuilder getSb() { + return sb; + } + + public void setSb(StringBuilder sb) { + this.sb = sb; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/SourceTargetMapper.java index 74e495081a..770c702f69 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/SourceTargetMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.string; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java index 5353d19cee..37d3c77b04 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java @@ -1,42 +1,32 @@ -/** - * Copyright 2012-2015 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.conversion.string; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.GeneratedSource; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class StringConversionTest { private static final String STRING_CONSTANT = "String constant"; - @Test + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + SourceTargetMapper.class ); + + @ProcessorTest public void shouldApplyStringConversions() { Source source = new Source(); source.setB( (byte) 1 ); @@ -55,6 +45,7 @@ public void shouldApplyStringConversions() { source.setBoolBool( Boolean.TRUE ); source.setC( 'G' ); source.setCc( 'H' ); + source.setSb( new StringBuilder( "SB" ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -75,9 +66,37 @@ public void shouldApplyStringConversions() { assertThat( target.getBoolBool() ).isEqualTo( "true" ); assertThat( target.getC() ).isEqualTo( "G" ); assertThat( target.getCc() ).isEqualTo( "H" ); + assertThat( target.getSb() ).isEqualTo( "SB" ); + } + + @IssueKey("2846") + @ProcessorTest + public void shouldNotApplyStringConversionsWhenNull() { + Source source = new Source(); + + Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getB() ).isEqualTo( "0" ); + assertThat( target.getBb() ).isNull(); + assertThat( target.getS() ).isEqualTo( "0" ); + assertThat( target.getSs() ).isNull(); + assertThat( target.getI() ).isEqualTo( "0" ); + assertThat( target.getIi() ).isNull(); + assertThat( target.getL() ).isEqualTo( "0" ); + assertThat( target.getLl() ).isNull(); + assertThat( target.getF() ).isEqualTo( "0.0" ); + assertThat( target.getFf() ).isNull(); + assertThat( target.getD() ).isEqualTo( "0.0" ); + assertThat( target.getDd() ).isNull(); + assertThat( target.getBool() ).isEqualTo( "false" ); + assertThat( target.getBoolBool() ).isNull(); + assertThat( target.getC() ).isEqualTo( String.valueOf( '\u0000' ) ); + assertThat( target.getCc() ).isNull(); + assertThat( target.getSb() ).isNull(); } - @Test + @ProcessorTest public void shouldApplyReverseStringConversions() { Target target = new Target(); target.setB( "1" ); @@ -96,6 +115,7 @@ public void shouldApplyReverseStringConversions() { target.setBoolBool( "true" ); target.setC( "G" ); target.setCc( "H" ); + target.setSb( "SB" ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -116,9 +136,10 @@ public void shouldApplyReverseStringConversions() { assertThat( source.getBoolBool() ).isEqualTo( true ); assertThat( source.getC() ).isEqualTo( 'G' ); assertThat( source.getCc() ).isEqualTo( 'H' ); + assertThat( source.getSb().toString() ).isEqualTo( "SB" ); } - @Test + @ProcessorTest @IssueKey( "328" ) public void stringShouldBeMappedToObjectByReference() { Target target = new Target(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Target.java index 983c58eadb..7d58e9c12b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Target.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.conversion.string; @@ -36,6 +23,7 @@ public class Target { private String boolBool; private String c; private String cc; + private String sb; private String object; public String getB() { @@ -173,4 +161,12 @@ public String getObject() { public void setObject(String object) { this.object = object; } + + public String getSb() { + return sb; + } + + public void setSb(String sb) { + this.sb = sb; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java new file mode 100644 index 0000000000..d7d3ef4142 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIConversionTest.java @@ -0,0 +1,56 @@ +/* + * 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.test.conversion.uri; + +import java.net.URI; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests conversions between {@link java.net.URI} and String. + * + * @author Maciej Kucharczyk + */ +@IssueKey("4018") +@WithClasses({ URISource.class, URITarget.class, URIMapper.class }) +public class URIConversionTest { + + @ProcessorTest + public void shouldApplyURIConversion() { + URISource source = new URISource(); + source.setUriA( URI.create( "https://mapstruct.org/" ) ); + + URITarget target = URIMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getUriA() ).isEqualTo( source.getUriA().toString() ); + } + + @ProcessorTest + public void shouldApplyReverseURIConversion() { + URITarget target = new URITarget(); + target.setUriA( "https://mapstruct.org/" ); + + URISource source = URIMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getUriA() ).isEqualTo( URI.create( target.getUriA() ) ); + } + + @ProcessorTest + public void shouldHandleInvalidURIString() { + URITarget target = new URITarget(); + target.setInvalidURI( "ht!tp://example.com" ); + + assertThatThrownBy( () -> URIMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( IllegalArgumentException.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.java new file mode 100644 index 0000000000..cac94a4efa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URIMapper.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.ap.test.conversion.uri; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface URIMapper { + + URIMapper INSTANCE = Mappers.getMapper( URIMapper.class ); + + URITarget sourceToTarget(URISource source); + + URISource targetToSource(URITarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.java new file mode 100644 index 0000000000..502169fccb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URISource.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.ap.test.conversion.uri; + +import java.net.URI; + +/** + * @author Maciej Kucharczyk + */ +public class URISource { + private URI uriA; + + private URI invalidURI; + + public URI getUriA() { + return uriA; + } + + public void setUriA(URI uriA) { + this.uriA = uriA; + } + + public URI getInvalidURI() { + return invalidURI; + } + + public void setInvalidURI(URI invalidURI) { + this.invalidURI = invalidURI; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.java new file mode 100644 index 0000000000..b221392127 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uri/URITarget.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.test.conversion.uri; + +/** + * @author Maciej Kucharczyk + */ +public class URITarget { + private String uriA; + private String invalidURI; + + public String getUriA() { + return uriA; + } + + public void setUriA(String uriA) { + this.uriA = uriA; + } + + public String getInvalidURI() { + return invalidURI; + } + + public void setInvalidURI(String invalidURI) { + this.invalidURI = invalidURI; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Source.java new file mode 100644 index 0000000000..ece0501a08 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Source.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.ap.test.conversion.url; + +import java.net.URL; + +/** + * @author Adam Szatyin + */ +public class Source { + private URL url; + + private URL invalidURL; + + public URL getURL() { + return url; + } + + public void setURL(URL url) { + this.url = url; + } + + public URL getInvalidURL() { + return invalidURL; + } + + public void setInvalidURL(URL invalidURL) { + this.invalidURL = invalidURL; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Target.java new file mode 100644 index 0000000000..060f5582e1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/Target.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.test.conversion.url; + +/** + * @author Adam Szatyin + */ +public class Target { + private String url; + private String invalidURL; + + public String getURL() { + return this.url; + } + + public void setURL(final String url) { + this.url = url; + } + + public String getInvalidURL() { + return this.invalidURL; + } + + public void setInvalidURL(final String invalidURL) { + this.invalidURL = invalidURL; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java new file mode 100644 index 0000000000..f5a3b7f7e8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java @@ -0,0 +1,76 @@ +/* + * 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.test.conversion.url; + +import java.net.MalformedURLException; +import java.net.URI; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests conversions between {@link java.net.URL} and String. + * + * @author Adam Szatyin + */ +@WithClasses({ Source.class, Target.class, URLMapper.class }) +public class URLConversionTest { + + @ProcessorTest + public void shouldApplyURLConversion() throws MalformedURLException { + Source source = new Source(); + source.setURL( URI.create( "https://mapstruct.org/" ).toURL() ); + + Target target = URLMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getURL() ).isEqualTo( source.getURL().toString() ); + } + + @ProcessorTest + public void shouldApplyReverseURLConversion() throws MalformedURLException { + Target target = new Target(); + target.setURL( "https://mapstruct.org/" ); + + Source source = URLMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getURL() ).isEqualTo( URI.create( target.getURL() ).toURL() ); + } + + @ProcessorTest + public void shouldHandleInvalidURLString() { + Target target = new Target(); + target.setInvalidURL( "xxxxxx://mapstruct.org/" ); + + assertThatThrownBy( () -> URLMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( RuntimeException.class ) + .getRootCause().isInstanceOf( MalformedURLException.class ); + } + + @ProcessorTest + public void shouldHandleInvalidURLStringWithMalformedURLException() { + Target target = new Target(); + target.setInvalidURL( "xxxxxx://mapstruct.org/" ); + + assertThatThrownBy( () -> URLMapper.INSTANCE.targetToSourceWithMalformedURLException( target ) ) + .isInstanceOf( MalformedURLException.class ); + } + + @ProcessorTest + public void shouldHandleNullURLString() { + Source source = new Source(); + + Target target = URLMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getURL() ).isNull(); + assertThat( target.getInvalidURL() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLMapper.java new file mode 100644 index 0000000000..e93c47726c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLMapper.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.ap.test.conversion.url; + +import java.net.MalformedURLException; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface URLMapper { + + URLMapper INSTANCE = Mappers.getMapper( URLMapper.class ); + + Target sourceToTarget(Source source); + + Source targetToSource(Target target); + + Source targetToSourceWithMalformedURLException(Target target) throws MalformedURLException; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDConversionTest.java new file mode 100644 index 0000000000..51eab29cda --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDConversionTest.java @@ -0,0 +1,56 @@ +/* + * 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.test.conversion.uuid; + +import java.util.UUID; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Tests conversions between {@link java.util.UUID} and String. + * + * @author Jason Bodnar + */ +@IssueKey("2391") +@WithClasses({ UUIDSource.class, UUIDTarget.class, UUIDMapper.class }) +public class UUIDConversionTest { + + @ProcessorTest + public void shouldApplyUUIDConversion() { + UUIDSource source = new UUIDSource(); + source.setUUIDA( UUID.randomUUID() ); + + UUIDTarget target = UUIDMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getUUIDA() ).isEqualTo( source.getUUIDA().toString() ); + } + + @ProcessorTest + public void shouldApplyReverseUUIDConversion() { + UUIDTarget target = new UUIDTarget(); + target.setUUIDA( UUID.randomUUID().toString() ); + + UUIDSource source = UUIDMapper.INSTANCE.targetToSource( target ); + + assertThat( source ).isNotNull(); + assertThat( source.getUUIDA() ).isEqualTo( UUID.fromString( target.getUUIDA() ) ); + } + + @ProcessorTest + public void shouldHandleInvalidUUIDString() { + UUIDTarget target = new UUIDTarget(); + target.setInvalidUUID( "XXXXXXXXX" ); + + assertThatThrownBy( () -> UUIDMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( IllegalArgumentException.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDMapper.java new file mode 100644 index 0000000000..8430b0fdce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDMapper.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.ap.test.conversion.uuid; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface UUIDMapper { + + UUIDMapper INSTANCE = Mappers.getMapper( UUIDMapper.class ); + + UUIDTarget sourceToTarget(UUIDSource source); + + UUIDSource targetToSource(UUIDTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDSource.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDSource.java new file mode 100644 index 0000000000..220ce5a76a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDSource.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.ap.test.conversion.uuid; + +import java.util.UUID; + +/** + * @author Jason Bodnar + */ +public class UUIDSource { + private UUID uuidA; + + private UUID invalidUUID; + + public UUID getUUIDA() { + return this.uuidA; + } + + public void setUUIDA(final UUID uuidA) { + this.uuidA = uuidA; + } + + public UUID getInvalidUUID() { + return invalidUUID; + } + + public void setInvalidUUID(final UUID invalidUUID) { + this.invalidUUID = invalidUUID; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDTarget.java new file mode 100644 index 0000000000..4fd2558141 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/uuid/UUIDTarget.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.test.conversion.uuid; + +/** + * @author Jason Bodnar + */ +public class UUIDTarget { + private String uuidA; + private String invalidUUID; + + public String getUUIDA() { + return this.uuidA; + } + + public void setUUIDA(final String uuidA) { + this.uuidA = uuidA; + } + + public String getInvalidUUID() { + return this.invalidUUID; + } + + public void setInvalidUUID(final String invalidUUID) { + this.invalidUUID = invalidUUID; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/Address.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/Address.java index 7c7d63aa48..fe46600813 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/Address.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/Address.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AddressDto.java index 4b9547e335..bc1c576582 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/AddressDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AddressDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; @@ -22,6 +9,13 @@ public class AddressDto { private String addressLine; + public AddressDto() { + } + + public AddressDto(String addressLine) { + this.addressLine = addressLine; + } + public String getAddressLine() { return addressLine; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.java new file mode 100644 index 0000000000..6da0d70c93 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapper.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.ap.test.decorator; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +@DecoratedWith(AnnotatedMapperDecorator.class) +public interface AnnotatedMapper { + + AnnotatedMapper INSTANCE = Mappers.getMapper( AnnotatedMapper.class ); + + Target toTarget(Source source); + + class Source { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Target { + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.java new file mode 100644 index 0000000000..6bf7a1fd66 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnnotatedMapperDecorator.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.ap.test.decorator; + +import org.mapstruct.AnnotateWith; + +@AnnotateWith(value = TestAnnotation.class, elements = @AnnotateWith.Element(strings = "decoratorValue")) +public abstract class AnnotatedMapperDecorator implements AnnotatedMapper { + + private final AnnotatedMapper delegate; + + public AnnotatedMapperDecorator(AnnotatedMapper delegate) { + this.delegate = delegate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnotherPersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnotherPersonMapper.java index 7c0f0de67c..960250a0d5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnotherPersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnotherPersonMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnotherPersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnotherPersonMapperDecorator.java index de13358c3e..0967bcf70f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/AnotherPersonMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/AnotherPersonMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java new file mode 100644 index 0000000000..1e3d574a6d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratedWithAnnotatedWithTest.java @@ -0,0 +1,32 @@ +/* + * 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.test.decorator; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotatedWith on decorator classes. + */ +@IssueKey("3659") +@WithClasses({ + TestAnnotation.class, + AnnotatedMapper.class, + AnnotatedMapperDecorator.class +}) +public class DecoratedWithAnnotatedWithTest { + + @ProcessorTest + public void shouldApplyAnnotationFromDecorator() { + Class implementationClass = AnnotatedMapper.INSTANCE.getClass(); + + assertThat( implementationClass ).hasAnnotation( TestAnnotation.class ); + assertThat( implementationClass.getAnnotation( TestAnnotation.class ).value() ).isEqualTo( "decoratorValue" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratorTest.java index 654da5262e..4e70581983 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/DecoratorTest.java @@ -1,37 +1,21 @@ -/** - * Copyright 2012-2015 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.decorator; -import static org.fest.assertions.Assertions.assertThat; - import java.util.Calendar; - import javax.tools.Diagnostic.Kind; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * Test for the application of decorators. @@ -45,10 +29,9 @@ AddressDto.class }) @IssueKey("163") -@RunWith(AnnotationProcessorTestRunner.class) public class DecoratorTest { - @Test + @ProcessorTest @WithClasses({ PersonMapper.class, PersonMapperDecorator.class @@ -56,7 +39,7 @@ public class DecoratorTest { public void shouldInvokeDecoratorMethods() { //given Calendar birthday = Calendar.getInstance(); - birthday.set( 1928, 4, 23 ); + birthday.set( 1928, Calendar.MAY, 23 ); Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); //when @@ -69,7 +52,7 @@ public void shouldInvokeDecoratorMethods() { assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest @WithClasses({ PersonMapper.class, PersonMapperDecorator.class @@ -86,7 +69,25 @@ public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest + @WithClasses({ + PersonMapper.class, + PersonMapperDecorator.class + }) + @IssueKey("765") + public void shouldDelegateNonDecoratedVoidMethodsToDefaultImplementation() { + //given + AddressDto addressDto = new AddressDto( "42 Ocean View Drive" ); + + //when + Address address = new Address( "Main Street" ); + PersonMapper.INSTANCE.updateAddressFromDto( addressDto, address ); + + //then + assertThat( address.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest @WithClasses({ AnotherPersonMapper.class, AnotherPersonMapperDecorator.class @@ -94,7 +95,7 @@ public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { public void shouldApplyDecoratorWithDefaultConstructor() { //given Calendar birthday = Calendar.getInstance(); - birthday.set( 1928, 4, 23 ); + birthday.set( 1928, Calendar.MAY, 23 ); Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); //when @@ -107,7 +108,7 @@ public void shouldApplyDecoratorWithDefaultConstructor() { assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest @WithClasses({ YetAnotherPersonMapper.class, YetAnotherPersonMapperDecorator.class @@ -115,7 +116,7 @@ public void shouldApplyDecoratorWithDefaultConstructor() { public void shouldApplyDelegateToClassBasedMapper() { //given Calendar birthday = Calendar.getInstance(); - birthday.set( 1928, 4, 23 ); + birthday.set( 1928, Calendar.MAY, 23 ); Person person = new Person2( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); @@ -130,7 +131,7 @@ public void shouldApplyDelegateToClassBasedMapper() { } @IssueKey("173") - @Test + @ProcessorTest @WithClasses({ Person2Mapper.class, Person2MapperDecorator.class, @@ -145,7 +146,7 @@ public void shouldApplyDelegateToClassBasedMapper() { public void shouldApplyCustomMappers() { //given Calendar birthday = Calendar.getInstance(); - birthday.set( 1928, 4, 23 ); + birthday.set( 1928, Calendar.MAY, 23 ); Person2 person = new Person2( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); person.setEmployer( new Employer( "ACME" ) ); person.setSportsClub( new SportsClub( "SC Duckburg" ) ); @@ -166,7 +167,7 @@ public void shouldApplyCustomMappers() { assertThat( personDto.getSportsClub().getName() ).isEqualTo( "SC Duckburg" ); } - @Test + @ProcessorTest @WithClasses({ ErroneousPersonMapper.class, ErroneousPersonMapperDecorator.class @@ -176,8 +177,8 @@ public void shouldApplyCustomMappers() { diagnostics = { @Diagnostic(type = ErroneousPersonMapper.class, kind = Kind.ERROR, - line = 27, - messageRegExp = "Specified decorator type is no subtype of the annotated mapper type") + line = 14, + message = "Specified decorator type is no subtype of the annotated mapper type.") } ) public void shouldRaiseErrorInCaseWrongDecoratorTypeIsGiven() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/Employer.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/Employer.java index d034157692..aef87b819e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/Employer.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/Employer.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/EmployerDto.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/EmployerDto.java index fa17955e03..3dca441eca 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/EmployerDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/EmployerDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/EmployerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/EmployerMapper.java index ffc0096ab5..697526d76c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/EmployerMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/EmployerMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/ErroneousPersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/ErroneousPersonMapper.java index b6d4f87286..1019951b9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/ErroneousPersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/ErroneousPersonMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/ErroneousPersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/ErroneousPersonMapperDecorator.java index 914acf7f37..54eb009527 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/ErroneousPersonMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/ErroneousPersonMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/Person.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/Person.java index 8325b9e735..b9773e0043 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/Person.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/Person.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2.java index f5787e0ce1..d08694b118 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2Mapper.java index 94e1e2c7ef..544f5be4ec 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2Mapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2MapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2MapperDecorator.java index bce9cf82ab..fa7cc12ac9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2MapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/Person2MapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonDto.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonDto.java index 188539ff9a..4036686828 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonDto2.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonDto2.java index bfe37e8949..4b1438e2b9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonDto2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonDto2.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapper.java index 6528b1719c..66ddbcd4f5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapper.java @@ -1,26 +1,14 @@ -/** - * Copyright 2012-2015 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.decorator; import org.mapstruct.DecoratedWith; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.factory.Mappers; @Mapper @@ -33,4 +21,6 @@ public interface PersonMapper { PersonDto personToPersonDto(Person person); AddressDto addressToAddressDto(Address address); + + void updateAddressFromDto(AddressDto dto, @MappingTarget Address address); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapperDecorator.java index 56d393e850..f3c3ca151f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/PersonMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/SportsClub.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/SportsClub.java index d12a64aa99..13b7259855 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/SportsClub.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/SportsClub.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/SportsClubDto.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/SportsClubDto.java index 7a947149ba..818454f74e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/SportsClubDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/SportsClubDto.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.java new file mode 100644 index 0000000000..742184d261 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/TestAnnotation.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.ap.test.decorator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Test annotation for testing @AnnotatedWith on decorators. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestAnnotation { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/YetAnotherPersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/YetAnotherPersonMapper.java index 3e3307dcde..fa41843773 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/YetAnotherPersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/YetAnotherPersonMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/YetAnotherPersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/YetAnotherPersonMapperDecorator.java index 7f34974d91..a22ce77827 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/YetAnotherPersonMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/YetAnotherPersonMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java new file mode 100644 index 0000000000..a1a754158b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithMapper.java @@ -0,0 +1,40 @@ +/* + * 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.test.decorator.jakarta.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +/** + * A mapper using Jakarta component model with a decorator. + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA) +@DecoratedWith(JakartaAnnotateWithWithMapperDecorator.class) +public interface JakartaAnnotateWithMapper { + + /** + * Maps a person to a person DTO. + * + * @param person the person to map + * @return the person DTO + */ + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + /** + * Maps an address to an address DTO. + * + * @param address the address to map + * @return the address DTO + */ + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.java new file mode 100644 index 0000000000..89a42aaf6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaAnnotateWithWithMapperDecorator.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.ap.test.decorator.jakarta.annotatewith; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; + +/** + * A decorator for {@link JakartaAnnotateWithMapper}. + */ +@AnnotateWith(value = TestAnnotation.class) +public abstract class JakartaAnnotateWithWithMapperDecorator implements JakartaAnnotateWithMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jakarta.annotatewith.JakartaAnnotateWithMapperImpl_") + private JakartaAnnotateWithMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..ac213d96a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/annotatewith/JakartaDecoratorAnnotateWithTest.java @@ -0,0 +1,105 @@ +/* + * 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.test.decorator.jakarta.annotatewith; + +import java.util.Calendar; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotateWith on decorator classes with Jakarta component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + JakartaAnnotateWithMapper.class, + TestAnnotation.class, + JakartaAnnotateWithWithMapperDecorator.class +}) +@ComponentScan(basePackageClasses = JakartaDecoratorAnnotateWithTest.class) +@Configuration +@WithJakartaInject +public class JakartaDecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Inject + @Named + private JakartaAnnotateWithMapper jakartaAnnotateWithMapper; + + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = jakartaAnnotateWithMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void hasCorrectImports() { + // check the decorator + generatedSource.forMapper( JakartaAnnotateWithMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@TestAnnotation" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + // check the plain mapper + generatedSource.forDecoratedMapper( JakartaAnnotateWithMapper.class ).content() + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/JakartaJsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/JakartaJsr330DecoratorTest.java new file mode 100644 index 0000000000..1d285af365 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/JakartaJsr330DecoratorTest.java @@ -0,0 +1,108 @@ +/* + * 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.test.decorator.jakarta.jsr330; + +import java.util.Calendar; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + PersonMapper.class, + PersonMapperDecorator.class +}) +@IssueKey("2567") +@ComponentScan(basePackageClasses = JakartaJsr330DecoratorTest.class) +@Configuration +@WithJakartaInject +public class JakartaJsr330DecoratorTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Inject + @Named + private PersonMapper personMapper; + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = personMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { + Address address = new Address( "42 Ocean View Drive" ); + + AddressDto addressDto = personMapper.addressToAddressDto( address ); + + assertThat( addressDto ).isNotNull(); + assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void hasCorrectImports() { + // check the decorator + generatedSource.forMapper( PersonMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + // check the plain mapper + generatedSource.forDecoratedMapper( PersonMapper.class ).content() + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Singleton" + lineSeparator() + "@Named" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapper.java new file mode 100644 index 0000000000..74ef38439f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/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.ap.test.decorator.jakarta.jsr330; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +@DecoratedWith(PersonMapperDecorator.class) +public interface PersonMapper { + + @Mapping( target = "name", ignore = true ) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapperDecorator.java new file mode 100644 index 0000000000..7642dc5d52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapperDecorator.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.ap.test.decorator.jakarta.jsr330; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +public abstract class PersonMapperDecorator implements PersonMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jakarta.jsr330.PersonMapperImpl_") + private PersonMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java index 758adea478..008f8cfe12 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/Jsr330DecoratorTest.java @@ -1,45 +1,22 @@ -/** - * Copyright 2012-2015 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.test.decorator.jsr330; -import java.util.Calendar; - -import javax.inject.Inject; -import javax.inject.Named; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.decorator.Address; import org.mapstruct.ap.test.decorator.AddressDto; import org.mapstruct.ap.test.decorator.Person; import org.mapstruct.ap.test.decorator.PersonDto; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; -import static org.fest.assertions.Assertions.assertThat; +import static java.lang.System.lineSeparator; /** * Test for the application of decorators using component model jsr330. @@ -55,56 +32,20 @@ PersonMapperDecorator.class }) @IssueKey("592") -@RunWith(AnnotationProcessorTestRunner.class) -@ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) -@Configuration +@WithJavaxInject public class Jsr330DecoratorTest { - @Inject - @Named - private PersonMapper personMapper; - private ConfigurableApplicationContext context; - - @Before - public void springUp() { - context = new AnnotationConfigApplicationContext( getClass() ); - context.getAutowireCapableBeanFactory().autowireBean( this ); - } - - @After - public void springDown() { - if ( context != null ) { - context.close(); - } - } - - @Test - public void shouldInvokeDecoratorMethods() { - //given - Calendar birthday = Calendar.getInstance(); - birthday.set( 1928, 4, 23 ); - Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); - - //when - PersonDto personDto = personMapper.personToPersonDto( person ); - - //then - assertThat( personDto ).isNotNull(); - assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); - assertThat( personDto.getAddress() ).isNotNull(); - assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); - } - - @Test - public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { - //given - Address address = new Address( "42 Ocean View Drive" ); - - //when - AddressDto addressDto = personMapper.addressToAddressDto( address ); - - //then - assertThat( addressDto ).isNotNull(); - assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @IssueKey("664") + @ProcessorTest + public void hasSingletonAnnotation() { + // check the decorator + generatedSource.forMapper( PersonMapper.class ).content() + .contains( "@Singleton" + lineSeparator() + "@Named" ); + // check the plain mapper + generatedSource.forDecoratedMapper( PersonMapper.class ).content() + .contains( "@Singleton" + lineSeparator() + "@Named" ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapper.java index 37a2e253dd..770393f817 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapper.java @@ -1,32 +1,20 @@ -/** - * Copyright 2012-2015 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.decorator.jsr330; import org.mapstruct.DecoratedWith; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; import org.mapstruct.ap.test.decorator.Address; import org.mapstruct.ap.test.decorator.AddressDto; import org.mapstruct.ap.test.decorator.Person; import org.mapstruct.ap.test.decorator.PersonDto; -@Mapper(componentModel = "jsr330") +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) @DecoratedWith(PersonMapperDecorator.class) public interface PersonMapper { diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapperDecorator.java index 1954148d10..98d39cc0c8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/PersonMapperDecorator.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.decorator.jsr330; diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.java new file mode 100644 index 0000000000..7e6ab8b7bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapper.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.ap.test.decorator.jsr330.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +/** + * A mapper using JSR-330 component model with a decorator. + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +@DecoratedWith(Jsr330AnnotateWithMapperDecorator.class) +public interface Jsr330AnnotateWithMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java new file mode 100644 index 0000000000..96cee9f824 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330AnnotateWithMapperDecorator.java @@ -0,0 +1,32 @@ +/* + * 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.test.decorator.jsr330.annotatewith; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; + +/** + * A decorator for {@link Jsr330AnnotateWithMapper}. + */ +@AnnotateWith(value = TestAnnotation.class) +public abstract class Jsr330AnnotateWithMapperDecorator implements Jsr330AnnotateWithMapper { + + @Inject + @Named("org.mapstruct.ap.test.decorator.jsr330.annotatewith.Jsr330AnnotateWithMapperImpl_") + private Jsr330AnnotateWithMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..6cb6384f85 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jsr330/annotatewith/Jsr330DecoratorAnnotateWithTest.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.ap.test.decorator.jsr330.annotatewith; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.test.decorator.TestAnnotation; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * Test for the application of @AnnotateWith on decorator classes with JSR-330 component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + Jsr330AnnotateWithMapper.class, + Jsr330AnnotateWithMapperDecorator.class, + TestAnnotation.class +}) +@WithJavaxInject +public class Jsr330DecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldContainCustomAnnotation() { + generatedSource.forMapper( Jsr330AnnotateWithMapper.class ) + .content() + .contains( "@TestAnnotation" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java deleted file mode 100644 index 73089b94ff..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapper.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.decorator.spring; - -import org.mapstruct.DecoratedWith; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.ap.test.decorator.Address; -import org.mapstruct.ap.test.decorator.AddressDto; -import org.mapstruct.ap.test.decorator.Person; -import org.mapstruct.ap.test.decorator.PersonDto; - -@Mapper(componentModel = "spring") -@DecoratedWith(PersonMapperDecorator.class) -public interface PersonMapper { - - @Mapping( target = "name", ignore = true ) - PersonDto personToPersonDto(Person person); - - AddressDto addressToAddressDto(Address address); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java deleted file mode 100644 index 7a9863e005..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/PersonMapperDecorator.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.decorator.spring; - -import org.mapstruct.ap.test.decorator.Person; -import org.mapstruct.ap.test.decorator.PersonDto; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; - -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; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java deleted file mode 100644 index 33968c6cff..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/SpringDecoratorTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright 2012-2015 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.ap.test.decorator.spring; - -import java.util.Calendar; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mapstruct.ap.test.decorator.Address; -import org.mapstruct.ap.test.decorator.AddressDto; -import org.mapstruct.ap.test.decorator.Person; -import org.mapstruct.ap.test.decorator.PersonDto; -import org.mapstruct.ap.testutil.IssueKey; -import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; - -import static org.fest.assertions.Assertions.assertThat; - -/** - * Test for the application of decorators using component model spring. - * - * @author Andreas Gudian - */ -@WithClasses({ - Person.class, - PersonDto.class, - Address.class, - AddressDto.class, - PersonMapper.class, - PersonMapperDecorator.class -}) -@IssueKey("592") -@RunWith(AnnotationProcessorTestRunner.class) -@ComponentScan(basePackageClasses = SpringDecoratorTest.class) -@Configuration -public class SpringDecoratorTest { - - @Autowired - private PersonMapper personMapper; - private ConfigurableApplicationContext context; - - @Before - public void springUp() { - context = new AnnotationConfigApplicationContext( getClass() ); - context.getAutowireCapableBeanFactory().autowireBean( this ); - } - - @After - public void springDown() { - if ( context != null ) { - context.close(); - } - } - - @Test - public void shouldInvokeDecoratorMethods() { - //given - Calendar birthday = Calendar.getInstance(); - birthday.set( 1928, 4, 23 ); - Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); - - //when - PersonDto personDto = personMapper.personToPersonDto( person ); - - //then - assertThat( personDto ).isNotNull(); - assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); - assertThat( personDto.getAddress() ).isNotNull(); - assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); - } - - @Test - public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { - //given - Address address = new Address( "42 Ocean View Drive" ); - - //when - AddressDto addressDto = personMapper.addressToAddressDto( address ); - - //then - assertThat( addressDto ).isNotNull(); - assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.java new file mode 100644 index 0000000000..0ba8e88299 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapper.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.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@DecoratedWith(AnnotateMapperDecorator.class) +public interface AnnotateMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.java new file mode 100644 index 0000000000..cff0a4c148 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/AnnotateMapperDecorator.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.test.decorator.spring.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Component; + +@AnnotateWith(value = Component.class, elements = @AnnotateWith.Element(strings = "decoratorComponent")) +@AnnotateWith(value = Primary.class) +public abstract class AnnotateMapperDecorator implements AnnotateMapper { + + @Autowired + @Qualifier("delegate") + private AnnotateMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.java new file mode 100644 index 0000000000..9e2e35676c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapper.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.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@DecoratedWith(CustomAnnotateMapperDecorator.class) +public interface CustomAnnotateMapper { + + @Mapping(target = "name", ignore = true) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.java new file mode 100644 index 0000000000..3668d98b04 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomAnnotateMapperDecorator.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.ap.test.decorator.spring.annotatewith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +@AnnotateWith(value = CustomComponent.class, elements = @AnnotateWith.Element(strings = "customComponentDecorator")) +@AnnotateWith(value = CustomPrimary.class) +public abstract class CustomAnnotateMapperDecorator implements CustomAnnotateMapper { + + @Autowired + @Qualifier("delegate") + private CustomAnnotateMapper delegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = delegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + return dto; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.java new file mode 100644 index 0000000000..be71249f2a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomComponent.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.ap.test.decorator.spring.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.stereotype.Component; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Component +public @interface CustomComponent { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.java new file mode 100644 index 0000000000..4e18bdbc7d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/CustomPrimary.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.ap.test.decorator.spring.annotatewith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Primary; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Primary +public @interface CustomPrimary { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java new file mode 100644 index 0000000000..7b3911c22b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/annotatewith/SpringDecoratorAnnotateWithTest.java @@ -0,0 +1,118 @@ +/* + * 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.test.decorator.spring.annotatewith; + +import java.util.Calendar; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of @AnnotateWith on decorator classes with Spring component model. + */ +@IssueKey("3659") +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + AnnotateMapper.class, + AnnotateMapperDecorator.class, + CustomComponent.class, + CustomPrimary.class, + CustomAnnotateMapper.class, + CustomAnnotateMapperDecorator.class +}) +@WithSpring +@ComponentScan(basePackageClasses = SpringDecoratorAnnotateWithTest.class) +@Configuration +public class SpringDecoratorAnnotateWithTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private AnnotateMapper annotateMapper; + + @Autowired + private CustomAnnotateMapper customAnnotateMapper; + + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldNotDuplicateComponentAnnotation() { + generatedSource.forMapper( AnnotateMapper.class ) + .content() + .contains( "@Component(value = \"decoratorComponent\")", "@Primary" ) + .doesNotContain( "@Component" + System.lineSeparator() ); + } + + @ProcessorTest + public void shouldNotDuplicateCustomComponentAnnotation() { + generatedSource.forMapper( CustomAnnotateMapper.class ) + .content() + .contains( "@CustomComponent(value = \"customComponentDecorator\")", "@CustomPrimary" ) + .doesNotContain( "@Component" ); + } + + @ProcessorTest + public void shouldInvokeAnnotateDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = annotateMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldInvokeCustomAnnotateDecoratorMethods() { + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + PersonDto personDto = customAnnotateMapper.personToPersonDto( person ); + + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapper.java new file mode 100644 index 0000000000..4ada1124c0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapper.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.ap.test.decorator.spring.constructor; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.CONSTRUCTOR) +@DecoratedWith(PersonMapperDecorator.class) +public interface PersonMapper { + + @Mapping( target = "name", ignore = true ) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapperDecorator.java new file mode 100644 index 0000000000..6c406e334d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/PersonMapperDecorator.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.ap.test.decorator.spring.constructor; + +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java new file mode 100644 index 0000000000..6d18555372 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/constructor/SpringDecoratorTest.java @@ -0,0 +1,93 @@ +/* + * 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.test.decorator.spring.constructor; + +import java.util.Calendar; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of decorators using component model spring. + * + * @author Andreas Gudian + */ +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + PersonMapper.class, + PersonMapperDecorator.class +}) +@IssueKey("592") +@ComponentScan(basePackageClasses = SpringDecoratorTest.class) +@Configuration +@WithSpring +public class SpringDecoratorTest { + + @Autowired + private PersonMapper personMapper; + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + //given + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + //when + PersonDto personDto = personMapper.personToPersonDto( person ); + + //then + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { + //given + Address address = new Address( "42 Ocean View Drive" ); + + //when + AddressDto addressDto = personMapper.addressToAddressDto( address ); + + //then + assertThat( addressDto ).isNotNull(); + assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapper.java new file mode 100644 index 0000000000..5cbe867e7a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapper.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.ap.test.decorator.spring.field; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.FIELD) +@DecoratedWith(PersonMapperDecorator.class) +public interface PersonMapper { + + @Mapping( target = "name", ignore = true ) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapperDecorator.java new file mode 100644 index 0000000000..315e1d0f26 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/PersonMapperDecorator.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.ap.test.decorator.spring.field; + +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java new file mode 100644 index 0000000000..ac0af86381 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/field/SpringDecoratorTest.java @@ -0,0 +1,93 @@ +/* + * 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.test.decorator.spring.field; + +import java.util.Calendar; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for the application of decorators using component model spring. + * + * @author Andreas Gudian + */ +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + PersonMapper.class, + PersonMapperDecorator.class +}) +@WithSpring +@IssueKey("592") +@ComponentScan(basePackageClasses = SpringDecoratorTest.class) +@Configuration +public class SpringDecoratorTest { + + @Autowired + private PersonMapper personMapper; + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + //given + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + //when + PersonDto personDto = personMapper.personToPersonDto( person ); + + //then + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { + //given + Address address = new Address( "42 Ocean View Drive" ); + + //when + AddressDto addressDto = personMapper.addressToAddressDto( address ); + + //then + assertThat( addressDto ).isNotNull(); + assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapper.java new file mode 100644 index 0000000000..20ac9d874c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapper.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.ap.test.decorator.spring.setter; + +import org.mapstruct.DecoratedWith; +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.SETTER) +@DecoratedWith(PersonMapperDecorator.class) +public interface PersonMapper { + + @Mapping( target = "name", ignore = true ) + PersonDto personToPersonDto(Person person); + + AddressDto addressToAddressDto(Address address); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapperDecorator.java new file mode 100644 index 0000000000..8f306284a1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/PersonMapperDecorator.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.ap.test.decorator.spring.setter; + +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; + +public abstract class PersonMapperDecorator implements PersonMapper { + + private PersonMapper decoratorDelegate; + + @Override + public PersonDto personToPersonDto(Person person) { + PersonDto dto = decoratorDelegate.personToPersonDto( person ); + dto.setName( person.getFirstName() + " " + person.getLastName() ); + + return dto; + } + + @Autowired + public void setDecoratorDelegate(@Qualifier("delegate") PersonMapper decoratorDelegate) { + this.decoratorDelegate = decoratorDelegate; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/SpringDecoratorTest.java b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/SpringDecoratorTest.java new file mode 100644 index 0000000000..c03b05395a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/spring/setter/SpringDecoratorTest.java @@ -0,0 +1,88 @@ +/* + * 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.test.decorator.spring.setter; + +import java.util.Calendar; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mapstruct.ap.test.decorator.Address; +import org.mapstruct.ap.test.decorator.AddressDto; +import org.mapstruct.ap.test.decorator.Person; +import org.mapstruct.ap.test.decorator.PersonDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithSpring; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Person.class, + PersonDto.class, + Address.class, + AddressDto.class, + PersonMapper.class, + PersonMapperDecorator.class +}) +@WithSpring +@IssueKey("3229") +@ComponentScan(basePackageClasses = SpringDecoratorTest.class) +@Configuration +public class SpringDecoratorTest { + + @Autowired + private PersonMapper personMapper; + private ConfigurableApplicationContext context; + + @BeforeEach + public void springUp() { + context = new AnnotationConfigApplicationContext( getClass() ); + context.getAutowireCapableBeanFactory().autowireBean( this ); + } + + @AfterEach + public void springDown() { + if ( context != null ) { + context.close(); + } + } + + @ProcessorTest + public void shouldInvokeDecoratorMethods() { + //given + Calendar birthday = Calendar.getInstance(); + birthday.set( 1928, Calendar.MAY, 23 ); + Person person = new Person( "Gary", "Crant", birthday.getTime(), new Address( "42 Ocean View Drive" ) ); + + //when + PersonDto personDto = personMapper.personToPersonDto( person ); + + //then + assertThat( personDto ).isNotNull(); + assertThat( personDto.getName() ).isEqualTo( "Gary Crant" ); + assertThat( personDto.getAddress() ).isNotNull(); + assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } + + @ProcessorTest + public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { + //given + Address address = new Address( "42 Ocean View Drive" ); + + //when + AddressDto addressDto = personMapper.addressToAddressDto( address ); + + //then + assertThat( addressDto ).isNotNull(); + assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/DefaultComponentModelMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/DefaultComponentModelMapperTest.java new file mode 100644 index 0000000000..2c860f05f7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/DefaultComponentModelMapperTest.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.ap.test.defaultcomponentmodel; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2277") +@WithClasses({ + Source.class, + Target.class, +}) +public class DefaultComponentModelMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + InstanceIterableMapper.class, + InstanceMapper.class, + NonInstanceIterableMapper.class, + NonInstanceMapper.class, + NonPublicIterableMapper.class, + NonPublicMapper.class, + }) + public void shouldGenerateCorrectMapperInstantiation() { + generatedSource.addComparisonToFixtureFor( + InstanceIterableMapper.class, + NonInstanceIterableMapper.class, + NonPublicIterableMapper.class + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/InstanceIterableMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/InstanceIterableMapper.java new file mode 100644 index 0000000000..b5b0506b62 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/InstanceIterableMapper.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.ap.test.defaultcomponentmodel; + +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = InstanceMapper.class) +public interface InstanceIterableMapper { + + List map(List list); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/InstanceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/InstanceMapper.java new file mode 100644 index 0000000000..902ed551cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/InstanceMapper.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.ap.test.defaultcomponentmodel; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface InstanceMapper { + + InstanceMapper INSTANCE = Mappers.getMapper( InstanceMapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonInstanceIterableMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonInstanceIterableMapper.java new file mode 100644 index 0000000000..6cdbe53006 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonInstanceIterableMapper.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.ap.test.defaultcomponentmodel; + +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = NonInstanceMapper.class) +public interface NonInstanceIterableMapper { + + List map(List list); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonInstanceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonInstanceMapper.java new file mode 100644 index 0000000000..0d7f68392c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonInstanceMapper.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.ap.test.defaultcomponentmodel; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface NonInstanceMapper { + + NonInstanceMapper MAPPER = Mappers.getMapper( NonInstanceMapper.class ); + + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonPublicIterableMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonPublicIterableMapper.java new file mode 100644 index 0000000000..20d6dbc1e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonPublicIterableMapper.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.ap.test.defaultcomponentmodel; + +import java.util.List; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(uses = NonPublicMapper.class) +public interface NonPublicIterableMapper { + + List map(List list); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonPublicMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonPublicMapper.java new file mode 100644 index 0000000000..36f9b978f2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/NonPublicMapper.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.ap.test.defaultcomponentmodel; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public abstract class NonPublicMapper { + + static final NonPublicMapper INSTANCE = Mappers.getMapper( NonPublicMapper.class ); + + public abstract Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/Source.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/Source.java new file mode 100644 index 0000000000..a6aa152e20 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/Source.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.ap.test.defaultcomponentmodel; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private final String id; + + public Source(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/Target.java b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/Target.java new file mode 100644 index 0000000000..da7bfdbed0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultcomponentmodel/Target.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.ap.test.defaultcomponentmodel; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String id; + + public Target(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryDts.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryDts.java index 7b7b6b1a2f..8caa4bd27a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryDts.java +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryDts.java @@ -1,28 +1,18 @@ -/** - * Copyright 2012-2015 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.defaultvalue; +import org.mapstruct.ap.test.defaultvalue.other.Continent; + public class CountryDts { private String code; private int id; private long zipcode; private String region; + private Continent continent; public String getCode() { return code; @@ -55,4 +45,12 @@ public String getRegion() { public void setRegion(String region) { this.region = region; } + + public Continent getContinent() { + return continent; + } + + public void setContinent(Continent continent) { + this.continent = continent; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryEntity.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryEntity.java index 90a9b7f3d2..e11810aa78 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryEntity.java +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryEntity.java @@ -1,28 +1,18 @@ -/** - * Copyright 2012-2015 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.defaultvalue; +import org.mapstruct.ap.test.defaultvalue.other.Continent; + public class CountryEntity { private String code; private Integer id; private long zipcode; private Region region; + private Continent continent; public String getCode() { return code; @@ -56,4 +46,11 @@ public void setRegion(Region region) { this.region = region; } + public Continent getContinent() { + return continent; + } + + public void setContinent(Continent continent) { + this.continent = continent; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapper.java index bea860abeb..820e05b2c6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapper.java @@ -1,20 +1,7 @@ -/** - * Copyright 2012-2015 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.defaultvalue; @@ -33,6 +20,7 @@ public abstract class CountryMapper { @Mapping( target = "id", defaultValue = "42" ), @Mapping( target = "zipcode", defaultValue = "1337" ), @Mapping( target = "region", defaultValue = "someRegion" ), + @Mapping( target = "continent", defaultValue = "EUROPE" ) } ) public abstract CountryDts mapToCountryDts(CountryEntity country); @@ -40,19 +28,19 @@ public abstract class CountryMapper { @Mapping( target = "code", defaultValue = "DE" ), @Mapping( target = "id", defaultValue = "42" ), @Mapping( target = "zipcode", defaultValue = "1337" ), - @Mapping( target = "region", ignore = true ) - + @Mapping( target = "region", ignore = true ), + @Mapping( target = "continent", defaultValue = "EUROPE" ) } ) - public abstract void mapToCountryDts(CountryDts countryDts, @MappingTarget CountryEntity country); + public abstract void mapToCountryEntity(CountryDts countryDts, @MappingTarget CountryEntity country); @Mappings( { @Mapping( target = "code", defaultValue = "DE" ), @Mapping( target = "id", defaultValue = "42" ), @Mapping( target = "zipcode", defaultValue = "1337" ), - @Mapping( target = "region", ignore = true ) - + @Mapping( target = "region", ignore = true ), + @Mapping( target = "continent", defaultValue = "EUROPE" ) } ) - public abstract void mapToCountryDts(CountryEntity source, @MappingTarget CountryEntity target); + public abstract void mapToCountryEntity(CountryEntity source, @MappingTarget CountryEntity target); protected String mapToString(Region region) { return ( region != null ) ? region.getCode() : null; diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapperMultipleSources.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapperMultipleSources.java new file mode 100644 index 0000000000..11a424d929 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/CountryMapperMultipleSources.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.ap.test.defaultvalue; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CountryMapperMultipleSources { + + CountryMapperMultipleSources INSTANCE = Mappers.getMapper( CountryMapperMultipleSources.class ); + + @Mapping(target = "code", defaultValue = "CH") + @Mapping(target = "region", source = "regionCode") + CountryDts map(CountryEntity entity, String regionCode); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/DefaultValueTest.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/DefaultValueTest.java index abf16906b3..114d762c03 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/DefaultValueTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/DefaultValueTest.java @@ -1,48 +1,32 @@ -/** - * Copyright 2012-2015 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.defaultvalue; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.mapstruct.ap.test.defaultvalue.other.Continent; import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; - -import java.text.ParseException; -import static org.fest.assertions.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; -@IssueKey( "600" ) -@RunWith( AnnotationProcessorTestRunner.class ) -@WithClasses( { - CountryEntity.class, - CountryDts.class -} ) +@IssueKey("600") +@WithClasses({ + CountryEntity.class, + CountryDts.class, + Continent.class +}) public class DefaultValueTest { - @Test - @WithClasses( { - Region.class, - CountryMapper.class - } ) + @ProcessorTest + @WithClasses({ + Region.class, + CountryMapper.class + }) /** * Checks: *