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/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/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/java-ea.yml b/.github/workflows/java-ea.yml new file mode 100644 index 0000000000..fc3a4a0fb1 --- /dev/null +++ b/.github/workflows/java-ea.yml @@ -0,0 +1,21 @@ +name: Java EA + +on: [push] + +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@v4 + - name: 'Set up JDK' + uses: oracle-actions/setup-java@v1 + 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..833bb6ba39 --- /dev/null +++ b/.github/workflows/macos.yml @@ -0,0 +1,20 @@ +name: Mac OS CI + +on: push + +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@v4 + - name: 'Set up JDK 21' + uses: actions/setup-java@v4 + 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..c4def2a89d --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,63 @@ +name: CI + +on: [push, pull_request] + +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] + name: 'Linux JDK ${{ matrix.java }}' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@v4 + - name: 'Set up JDK' + uses: actions/setup-java@v4 + 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@v5 + 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, 11, 17 ] + name: 'Linux JDK ${{ matrix.java }}' + runs-on: ubuntu-latest + steps: + - name: 'Checkout' + uses: actions/checkout@v4 + - name: 'Set up JDK 21 for building everything' + uses: actions/setup-java@v4 + 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@v4 + 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..666a74656d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,119 @@ +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 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Java + uses: actions/setup-java@v4 + 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@v4 + with: + name: jreleaser-release + path: | + parent/target/jreleaser/trace.log + parent/target/jreleaser/output.properties + + - name: Reset NEXT_RELEASE_CHANGELOG.md + run: | + echo -e "### Features\n\n### Enhancements\n\n### Bugs\n\n### Documentation\n\n### Build\n" > NEXT_RELEASE_CHANGELOG.md + + - name: Set next version + run: | + ./mvnw -ntp -B versions:set versions:commit -DnewVersion=${{ env.NEXT_VERSION }} -pl :mapstruct-parent -DgenerateBackupPoms=false + sed -i -e "s@project.build.outputTimestamp>.*\${git.commit.author.time} org.mapstruct mapstruct-parent - 1.4.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index 9051361d18..71a7d35899 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,8 +71,6 @@ - - @@ -115,10 +124,6 @@ - - - - @@ -141,7 +146,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 6757936ad1..356deb904d 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,40 +1,66 @@ Contributors ============ +Aleksey Ivashin - https://github.com/xumk Alexandr Shalugin - https://github.com/shalugin +Amine Touzani - https://github.com/ttzn Andreas Gudian - https://github.com/agudian +Andrei Arlou - https://github.com/Captain1653 Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon Arne Seime - https://github.com/seime +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 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 +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 Michael Pardo - https://github.com/pardom +Muhammad Usama - https://github.com/the-mgi Mustafa Caylak - https://github.com/luxmeter 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 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 @@ -44,7 +70,17 @@ Sjaak Derksen - https://github.com/sjaakd Stefan May - https://github.com/osthus-sm 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 +Zegveld - https://github.com/Zegveld +znight1020 - https://github.com/znight1020 +zral - https://github.com/zyberzebra diff --git a/core-jdk8/pom.xml b/core-jdk8/pom.xml index 63311b5950..c73676192b 100644 --- a/core-jdk8/pom.xml +++ b/core-jdk8/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.4.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml diff --git a/core/pom.xml b/core/pom.xml index c74e0b8bb1..e62d5e4682 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.4.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -22,8 +22,8 @@ - junit - junit + org.junit.jupiter + junit-jupiter test 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 index 6b062dac99..309458f861 100644 --- a/core/src/main/java/org/mapstruct/BeanMapping.java +++ b/core/src/main/java/org/mapstruct/BeanMapping.java @@ -11,13 +11,46 @@ 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 */ @@ -27,6 +60,8 @@ /** * Specifies the result type of the factory method to be used in case several factory methods qualify. + *

+ * NOTE: This property is not inherited to generated mapping methods * * @return the resultType to select */ @@ -40,9 +75,9 @@ * A qualifier is a custom annotation and can be placed on either a hand written mapper class or a method. * * @return the qualifiers - * @see BeanMapping#qualifiedByName() + * @see Qualifier */ - Class[] qualifiedBy() default { }; + Class[] qualifiedBy() default {}; /** * Similar to {@link #qualifiedBy()}, but used in combination with {@code @}{@link Named} in case no custom @@ -86,9 +121,32 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() */ NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; + /** + * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}. + * + * Overrides the setting on {@link MapperConfig} and {@link Mapper}. + * + * @return strategy to handle missing implementation combined with {@link SubclassMappings}. + * + * @since 1.5 + */ + SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

+ * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class subclassExhaustiveException() default IllegalArgumentException.class; + /** * Default ignore all mappings. All mappings have to be defined manually. No automatic mapping will take place. No - * warning will be issued on missing target properties. + * warning will be issued on missing source or target properties. * * @return The ignore strategy (default false). * @@ -103,6 +161,8 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() * source properties report. *

* NOTE: This does not support ignoring nested source properties + *

+ * NOTE: This property is not inherited to generated mapping methods * * @return The source properties that should be ignored when performing a report * @@ -110,6 +170,28 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() */ String[] ignoreUnmappedSourceProperties() default {}; + /** + * How unmapped properties of the source type of a mapping should be reported. + * If no policy is configured, the policy given via {@link MapperConfig#unmappedSourcePolicy()} or + * {@link Mapper#unmappedSourcePolicy()} will be applied, using {@link ReportingPolicy#IGNORE} by default. + * + * @return The reporting policy for unmapped source properties. + * + * @since 1.6 + */ + ReportingPolicy unmappedSourcePolicy() default ReportingPolicy.IGNORE; + + /** + * How unmapped properties of the target type of a mapping should be reported. + * If no policy is configured, the policy given via {@link MapperConfig#unmappedTargetPolicy()} or + * {@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. @@ -128,4 +210,18 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() * @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/Builder.java b/core/src/main/java/org/mapstruct/Builder.java index ec26ab37ab..449c4ac05c 100644 --- a/core/src/main/java/org/mapstruct/Builder.java +++ b/core/src/main/java/org/mapstruct/Builder.java @@ -14,6 +14,32 @@ /** * 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 @@ -29,4 +55,12 @@ * @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/CollectionMappingStrategy.java b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java index afaba97370..0ea3ee7df5 100644 --- a/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/CollectionMappingStrategy.java @@ -7,6 +7,54 @@ /** * Strategy for propagating the value of collection-typed properties from source to target. + *

+ * In the table below, the dash {@code -} indicates a property name. + * Next, the trailing {@code s} indicates the plural form. + * The table explains the options and how they are applied to the presence / absence of a + * {@code set-s}, {@code add-} and / or {@code get-s} method on the target object. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Collection mapping strategy options
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 */ 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/DecoratedWith.java b/core/src/main/java/org/mapstruct/DecoratedWith.java index e2734a699c..3db27b2a0a 100644 --- a/core/src/main/java/org/mapstruct/DecoratedWith.java +++ b/core/src/main/java/org/mapstruct/DecoratedWith.java @@ -22,9 +22,7 @@ *

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

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

*

Examples

*

* For the examples below, consider the following mapper declaration: @@ -105,12 +103,12 @@ * private PersonMapper personMapper; // injects the decorator, with the injected original mapper * * - *

3. Component model 'jsr330'

+ *

3. Component model 'jsr330' or 'jakarta'

*

Referencing the original mapper

*

- * JSR 330 doesn't specify qualifiers and only allows to specifically name the beans. Hence, the generated - * implementation of the original mapper is annotated with - * {@code @javax.inject.Named("fully-qualified-name-of-generated-impl")} and {@code @Singleton} (please note that when + * JSR 330 / Jakarta Inject doesn't specify qualifiers and only allows to specifically name the beans. Hence, + * the generated implementation of the original mapper is annotated with + * {@code @Named("fully-qualified-name-of-generated-impl")} and {@code @Singleton} (please note that when * using a decorator, the class name of the mapper implementation ends with an underscore). To inject that bean in your * decorator, add the same annotation to the delegate field (e.g. by copy/pasting it from the generated class): * @@ -142,12 +140,11 @@ * @javax.inject.Named * private PersonMapper personMapper; // injects the decorator, with the injected original mapper * - *

* * @author Gunnar Morling */ @Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) +@Retention(RetentionPolicy.CLASS) public @interface DecoratedWith { /** diff --git a/core/src/main/java/org/mapstruct/EnumMapping.java b/core/src/main/java/org/mapstruct/EnumMapping.java new file mode 100644 index 0000000000..375f969b01 --- /dev/null +++ b/core/src/main/java/org/mapstruct/EnumMapping.java @@ -0,0 +1,156 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configured the mapping between two value types. + *

Example: Using a suffix for enums

+ *

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

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

+ * Note: + *

    + *
  • + * The defined exception should at least have a constructor with a {@link String} parameter. + *
  • + *
  • + * If the defined exception is a checked exception then the enum mapping methods should have that exception + * in the throws clause. + *
  • + *
+ * + * @return the exception that should be used in the generated code + */ + Class unexpectedValueMappingException() default IllegalArgumentException.class; +} diff --git a/core/src/main/java/org/mapstruct/Ignored.java b/core/src/main/java/org/mapstruct/Ignored.java new file mode 100644 index 0000000000..47e4961f43 --- /dev/null +++ b/core/src/main/java/org/mapstruct/Ignored.java @@ -0,0 +1,67 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configures the ignored of one bean attribute. + * + *

+ * The name all attributes of for ignored is to be specified via {@link #targets()}. + *

+ * + *

+ * Example 1: Implicitly mapping fields with the same name: + *

+ * + *

+ * // We need ignored Human.name and Human.lastName
+ * // we can use @Ignored with parameters "name", "lastName" {@link #targets()}
+ * @Mapper
+ * public interface HumanMapper {
+ *    @Ignored( targets = { "name", "lastName" } )
+ *    HumanDto toHumanDto(Human human)
+ * }
+ * 
+ *

+ * // generates:
+ * @Override
+ * public HumanDto toHumanDto(Human human) {
+ *    humanDto.setFullName( human.getFullName() );
+ *    // ...
+ * }
+ * 
+ * + * @author Ivashin Aleksey + */ +@Repeatable(IgnoredList.class) +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) +public @interface Ignored { + + /** + * Whether the specified properties should be ignored by the generated mapping method. + * This can be useful when certain attributes should not be propagated from source to target or when properties in + * the target object are populated using a decorator and thus would be reported as unmapped target property by + * default. + * + * @return The target names of the configured properties that should be ignored + */ + String[] targets(); + + /** + * The prefix that should be applied to all the properties specified via {@link #targets()}. + * + * @return The target prefix to be applied to the defined properties + */ + String prefix() default ""; + +} diff --git a/core/src/main/java/org/mapstruct/IgnoredList.java b/core/src/main/java/org/mapstruct/IgnoredList.java new file mode 100644 index 0000000000..e47db6bac7 --- /dev/null +++ b/core/src/main/java/org/mapstruct/IgnoredList.java @@ -0,0 +1,54 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Configures the ignored list for several bean attributes. + *

+ * TIP: When using Java 8 or later, you can omit the {@code @IgnoredList} + * wrapper annotation and directly specify several {@code @Ignored} annotations on one method. + * + *

These two examples are equal. + *

+ *

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

+ * // Java 8 and later
+ * @Mapper
+ * public interface MyMapper {
+ *     @Ignored(targets = { "firstProperty" } ),
+ *     @Ignored(targets = { "secondProperty" } )
+ *     HumanDto toHumanDto(Human human);
+ * }
+ * 
+ * + * @author Ivashin Aleksey + */ +@Retention(RetentionPolicy.CLASS) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE }) +public @interface IgnoredList { + + /** + * The configuration of the bean attributes. + * + * @return The configuration of the bean attributes. + */ + Ignored[] value(); +} diff --git a/core/src/main/java/org/mapstruct/InheritConfiguration.java b/core/src/main/java/org/mapstruct/InheritConfiguration.java index d39ded7b0d..0d1bf0b38f 100644 --- a/core/src/main/java/org/mapstruct/InheritConfiguration.java +++ b/core/src/main/java/org/mapstruct/InheritConfiguration.java @@ -18,6 +18,10 @@ * 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: * diff --git a/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java b/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java index d1bede3f05..b659b7f37a 100644 --- a/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java +++ b/core/src/main/java/org/mapstruct/InheritInverseConfiguration.java @@ -21,7 +21,59 @@ *

* 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) @@ -29,8 +81,8 @@ public @interface InheritInverseConfiguration { /** - * The name of the inverse mapping method to inherit the mappings from. Needs only to be specified in case more than - * one inverse method with matching source and target type exists. + * The name of the inverse mapping method to inherit the mappings from. Needs to be specified only in case more than + * one inverse method exists with a matching source and target type exists. * * @return The name of the inverse mapping method to inherit the mappings from. */ diff --git a/core/src/main/java/org/mapstruct/InjectionStrategy.java b/core/src/main/java/org/mapstruct/InjectionStrategy.java index 84baa8afa9..f5029e2246 100644 --- a/core/src/main/java/org/mapstruct/InjectionStrategy.java +++ b/core/src/main/java/org/mapstruct/InjectionStrategy.java @@ -7,9 +7,10 @@ /** * Strategy for handling injection. This is only used on annotated based component models such as CDI, Spring and - * JSR330. + * JSR330 / Jakarta. * * @author Kevin Grüneberg + * @author Lucas Resch */ public enum InjectionStrategy { @@ -17,5 +18,8 @@ public enum InjectionStrategy { FIELD, /** Annotations are written on the constructor **/ - CONSTRUCTOR + CONSTRUCTOR, + + /** A dedicated setter method is created */ + SETTER } diff --git a/core/src/main/java/org/mapstruct/IterableMapping.java b/core/src/main/java/org/mapstruct/IterableMapping.java index 0e81e66126..d644dfe03b 100644 --- a/core/src/main/java/org/mapstruct/IterableMapping.java +++ b/core/src/main/java/org/mapstruct/IterableMapping.java @@ -10,17 +10,43 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.text.SimpleDateFormat; import java.text.DecimalFormat; +import java.text.SimpleDateFormat; import java.util.Date; +import org.mapstruct.control.MappingControl; + /** * Configures the mapping between two iterable like types, e.g. {@code List} and {@code List}. * * - *

Note: either @IterableMapping#dateFormat, @IterableMapping#resultType or @IterableMapping#qualifiedBy + *

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: * - * The method overrides an unmappedTargetPolicy set in a central configuration set + * 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 "default"; + String componentModel() default MappingConstants.ComponentModel.DEFAULT; /** * Specifies the name of the implementation class. The {@code } will be replaced by the @@ -148,6 +209,32 @@ */ NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** + * The strategy to be applied when {@code null} is passed as source argument value to an {@link IterableMapping} of + * this mapper. If unset, the strategy set with {@link #nullValueMappingStrategy()} will be applied. If neither + * strategy is configured, the strategy given via {@link MapperConfig#nullValueIterableMappingStrategy()} will be + * applied, using {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @since 1.5 + * + * @return The strategy to be applied when {@code null} is passed as source value to an {@link IterableMapping} of + * this mapper. + */ + NullValueMappingStrategy nullValueIterableMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + + /** + * The strategy to be applied when {@code null} is passed as source argument value to a {@link MapMapping} of this + * mapper. If unset, the strategy set with {@link #nullValueMappingStrategy()} will be applied. If neither strategy + * is configured, the strategy given via {@link MapperConfig#nullValueMapMappingStrategy()} will be applied, using + * {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @since 1.5 + * + * @return The strategy to be applied when {@code null} is passed as source value to a {@link MapMapping} of this + * mapper. + */ + NullValueMappingStrategy nullValueMapMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** * The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is * configured, the strategy given via {@link MapperConfig#nullValuePropertyMappingStrategy()} will be applied, @@ -183,6 +270,29 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default */ NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; + /** + * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}. + * + * Can be overridden by the one on {@link BeanMapping}, but overrides {@link MapperConfig}. + * + * @return strategy to handle missing implementation combined with {@link SubclassMappings}. + * + * @since 1.5 + */ + SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

+ * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class 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. @@ -201,7 +311,7 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default * Can be configured by the {@link MapperConfig#disableSubMappingMethodsGeneration()} as well. *

* Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at - * mapstruct.org or + * mapstruct.org or * github.com/mapstruct/mapstruct to share what problem you * are facing with the automatic sub-mapping generation. * @@ -229,4 +339,53 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default * @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; } diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 079388cc42..8631562a56 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -5,14 +5,17 @@ */ 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.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 @@ -30,6 +33,36 @@ * 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() */ @@ -51,6 +84,8 @@ * 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 { }; @@ -96,12 +131,17 @@ * can be retrieved via {@code @Autowired} *
  • * {@code jsr330}: the generated mapper is annotated with {@code @javax.inject.Named} and - * {@code @Singleton}, and can be retrieved via {@code @Inject}
  • + * {@code @Singleton}, and can be retrieved via {@code @Inject}. + * The annotations will either be from javax.inject or jakarta.inject, + * depending on which one is available, with javax.inject having precedence. + *
  • + * {@code jakarta}: the generated mapper is annotated with {@code @jakarta.inject.Named} and + * {@code @Singleton}, and can be retrieved via {@code @Inject}.
  • * * * @return The component model for the generated mapper. */ - String componentModel() default "default"; + String componentModel() default MappingConstants.ComponentModel.DEFAULT; /** * Specifies the name of the implementation class. The {@code } will be replaced by the @@ -142,6 +182,28 @@ */ NullValueMappingStrategy nullValueMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** + * The strategy to be applied when {@code null} is passed as source argument value to an {@link IterableMapping}. + * If no strategy is configured, the strategy given via {@link #nullValueMappingStrategy()} will be applied, using + * {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @since 1.5 + * + * @return The strategy to be applied when {@code null} is passed as source value to an {@link IterableMapping}. + */ + NullValueMappingStrategy nullValueIterableMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + + /** + * The strategy to be applied when {@code null} is passed as source argument value to a {@link MapMapping}. + * If no strategy is configured, the strategy given via {@link #nullValueMappingStrategy()} will be applied, using + * {@link NullValueMappingStrategy#RETURN_NULL} by default. + * + * @since 1.5 + * + * @return The strategy to be applied when {@code null} is passed as source value to a {@link MapMapping}. + */ + NullValueMappingStrategy nullValueMapMappingStrategy() default NullValueMappingStrategy.RETURN_NULL; + /** * The strategy to be applied when a source bean property is {@code null} or not present. If no strategy is * configured, {@link NullValuePropertyMappingStrategy#SET_TO_NULL} will be used by default. @@ -176,6 +238,29 @@ MappingInheritanceStrategy mappingInheritanceStrategy() */ NullValueCheckStrategy nullValueCheckStrategy() default ON_IMPLICIT_CONVERSION; + /** + * Determines how to handle missing implementation for super classes when using the {@link SubclassMapping}. + * + * Can be overridden by the one on {@link BeanMapping} or {@link Mapper}. + * + * @return strategy to handle missing implementation combined with {@link SubclassMappings}. + * + * @since 1.5 + */ + SubclassExhaustiveStrategy subclassExhaustiveStrategy() default COMPILE_ERROR; + + /** + * Specifies the exception type to be thrown when a missing subclass implementation is detected + * in combination with {@link SubclassMappings}, based on the {@link #subclassExhaustiveStrategy()}. + *

    + * This exception will only be thrown when the {@code subclassExhaustiveStrategy} is set to + * {@link SubclassExhaustiveStrategy#RUNTIME_EXCEPTION}. + * + * @return the exception class to throw when missing implementations are found. + * Defaults to {@link IllegalArgumentException}. + */ + Class 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. @@ -196,7 +281,7 @@ MappingInheritanceStrategy mappingInheritanceStrategy() * Can be overridden by {@link Mapper#disableSubMappingMethodsGeneration()} *

    * Note: If you need to use {@code disableSubMappingMethodsGeneration} please contact the MapStruct team at - * mapstruct.org or + * mapstruct.org or * github.com/mapstruct/mapstruct to share what problem you * are facing with the automatic sub-mapping generation. * @@ -225,4 +310,52 @@ MappingInheritanceStrategy mappingInheritanceStrategy() * @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; } + diff --git a/core/src/main/java/org/mapstruct/Mapping.java b/core/src/main/java/org/mapstruct/Mapping.java index 01ffcd17a1..c2a6172e67 100644 --- a/core/src/main/java/org/mapstruct/Mapping.java +++ b/core/src/main/java/org/mapstruct/Mapping.java @@ -15,27 +15,133 @@ import java.text.SimpleDateFormat; import java.util.Date; +import org.mapstruct.control.MappingControl; + import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; + /** - * Configures the mapping of one bean attribute or enum constant. + * Configures the mapping of one bean attribute. *

    * The name of the mapped attribute or constant is to be specified via {@link #target()}. For mapped bean attributes it * is assumed by default that the attribute has the same name in the source bean. Alternatively, one of * {@link #source()}, {@link #expression()} or {@link #constant()} can be specified to define the property source. + *

    *

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

    * *

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

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

    Example 2: Mapping properties with different names

    + *
    
    + * // We need map Human.companyName to HumanDto.company
    + * // we can use @Mapping with parameters {@link #source()} and {@link #target()}
    + * @Mapper
    + * public interface HumanMapper {
    + *    @Mapping(source="companyName", target="company")
    + *    HumanDto toHumanDto(Human human)
    + * }
    + * 
    + *
    
    + * // generates:
    + * @Override
    + * public HumanDto toHumanDto(Human human) {
    + *     humanDto.setCompany( human.getCompanyName() );
    + *      // ...
    + * }
    + * 
    + *

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

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

    + * Example 4: Mapping to constant + *

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

    + * Example 5: Mapping with default value + *

    + *
    
    + * // We need map Human.name to HumanDto.fullName, but if Human.name == null, then set value "Somebody"
    + * // we can use {@link #defaultValue()} or {@link #defaultExpression()} for it
    + * @Mapper
    + * public interface HumanMapper {
    + *    @Mapping(source="name", target="fullName", defaultValue="Somebody")
    + *    HumanDto toHumanDto(Human human)
    + * }
    + * 
    + *
    
    + * // generates
    + * @Override
    + * public HumanDto toHumanDto(Human human) {
    + *    if ( human.getName() != null ) {
    + *       humanDto.setFullName( human.getName() );
    + *    }
    + *    else {
    + *       humanDto.setFullName( "Somebody" );
    + *    }
    + *   // ...
    + * }
    + * 
    * * @author Gunnar Morling */ @Repeatable(Mappings.class) @Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) +@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE }) public @interface Mapping { /** @@ -69,19 +175,38 @@ /** * A format string as processable by {@link SimpleDateFormat} if the attribute is mapped from {@code String} to * {@link Date} or vice-versa. Will be ignored for all other attribute types and when mapping enum constants. + *

    + * If the {@link #locale()} is also specified, the format will consider the specified locale when processing + * the date. Otherwise, the system's default locale will be used. * * @return A date format string as processable by {@link SimpleDateFormat}. + * @see #locale() */ String dateFormat() default ""; /** * A format string as processable by {@link DecimalFormat} if the annotated method maps from a * {@link Number} to a {@link String} or vice-versa. Will be ignored for all other element types. + *

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

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

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

    @@ -105,10 +230,13 @@ *

    * MapStruct handles the constant as {@code String}. The value will be converted by applying a matching method, * type conversion method or built-in conversion. - *

    * * *

    + * You can use {@link #qualifiedBy()} or {@link #qualifiedByName()} to force the use of a conversion method + * even when one would not apply. (e.g. {@code String} to {@code String}) + *

    + *

    * This attribute can not be used together with {@link #source()}, {@link #defaultValue()}, * {@link #defaultExpression()} or {@link #expression()}. * @@ -136,7 +264,7 @@ * imported via {@link Mapper#imports()}. *

    * This attribute can not be used together with {@link #source()}, {@link #defaultValue()}, - * {@link #defaultExpression()} or {@link #constant()}. + * {@link #defaultExpression()}, {@link #qualifiedBy()}, {@link #qualifiedByName()} or {@link #constant()}. * * @return An expression specifying the value for the designated target property */ @@ -174,9 +302,12 @@ /** * Whether the property specified via {@link #target()} should be ignored by the generated mapping method or not. - * This can be useful when certain attributes should not be propagated from source or target or when properties in + * This can be useful when certain attributes should not be propagated from source to target or when properties in * the target object are populated using a decorator and thus would be reported as unmapped target property by * default. + *

    + * If you have multiple properties to ignore, + * you can use the {@link Ignored} annotation instead and group them all at once. * * @return {@code true} if the given property should be ignored, {@code false} otherwise */ @@ -186,8 +317,11 @@ * A qualifier can be specified to aid the selection process of a suitable mapper. This is useful in case multiple * mapping methods (hand written or generated) qualify and thus would result in an 'Ambiguous mapping methods found' * error. A qualifier is a custom annotation and can be placed on a hand written mapper class or a method. + *

    + * Note that {@link #defaultValue()} usage will also be converted using this qualifier. * * @return the qualifiers + * @see Qualifier */ Class[] qualifiedBy() default { }; @@ -199,6 +333,8 @@ * Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and * are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large * number of qualifiers as no custom annotation types are needed. + *

    + * Note that {@link #defaultValue()} usage will also be converted using this qualifier. * * @return One or more qualifier name(s) * @see #qualifiedBy() @@ -206,6 +342,73 @@ */ 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. * @@ -242,13 +445,11 @@ * If not possible, MapStruct will try to apply a user defined mapping method. * * - *

    * *

  • other *

    * MapStruct handles the constant as {@code String}. The value will be converted by applying a matching method, * type conversion method or built-in conversion. - *

    *

  • * *

    @@ -286,4 +487,17 @@ 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 index 8b0da9a72e..3d3d8a4c77 100644 --- a/core/src/main/java/org/mapstruct/MappingConstants.java +++ b/core/src/main/java/org/mapstruct/MappingConstants.java @@ -23,12 +23,132 @@ private MappingConstants() { /** * 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/MappingTarget.java b/core/src/main/java/org/mapstruct/MappingTarget.java index fc140f120a..3916448368 100644 --- a/core/src/main/java/org/mapstruct/MappingTarget.java +++ b/core/src/main/java/org/mapstruct/MappingTarget.java @@ -17,6 +17,43 @@ *

    * 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) diff --git a/core/src/main/java/org/mapstruct/Mappings.java b/core/src/main/java/org/mapstruct/Mappings.java index 1e9dd96707..1578a648a9 100644 --- a/core/src/main/java/org/mapstruct/Mappings.java +++ b/core/src/main/java/org/mapstruct/Mappings.java @@ -12,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 index 0b6e8b53a9..773886a7b1 100644 --- a/core/src/main/java/org/mapstruct/Named.java +++ b/core/src/main/java/org/mapstruct/Named.java @@ -14,7 +14,7 @@ * Marks mapping methods with the given qualifier name. Can be used to qualify a single method or all methods of a given * type by specifying this annotation on the type level. *

    - * Will be used to to select the correct mapping methods when mapping a bean property type, element of an iterable type + * 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 diff --git a/core/src/main/java/org/mapstruct/NullEnum.java b/core/src/main/java/org/mapstruct/NullEnum.java new file mode 100644 index 0000000000..ac39b3485d --- /dev/null +++ b/core/src/main/java/org/mapstruct/NullEnum.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * To be used as a default value for enum class annotation elements. + * + * @author Ben Zegveld + * @since 1.6 + */ +enum NullEnum { +} diff --git a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java index 446b879d92..22dba7c58b 100644 --- a/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValueCheckStrategy.java @@ -8,7 +8,7 @@ /** * Strategy for dealing with null source values. * - * Note: This strategy is not in effect when the a specific source presence check method is defined + * Note: This strategy is not in effect when a specific source presence check method is defined * in the service provider interface (SPI). *

    * Note: some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder diff --git a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java index 519a28bd81..4cece83031 100644 --- a/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValueMappingStrategy.java @@ -13,8 +13,9 @@ public enum NullValueMappingStrategy { /** - * If {@code null} is passed to a mapping method, {@code null} will be returned. That's the default behavior if no - * alternative strategy is configured globally, for given mapper or method. + * If {@code null} is passed to a mapping method, {@code null} will be returned, unless the return type is + * {@link java.util.Optional Optional}, in which case an empty optional will be returned. + * That's the default behavior if no alternative strategy is configured globally, for given mapper or method. */ RETURN_NULL, @@ -28,6 +29,7 @@ public enum NullValueMappingStrategy { * case. *

  • For iterable mapping methods an empty collection will be returned.
  • *
  • For map mapping methods an empty map will be returned.
  • + *
  • For optional mapping methods an empty optional will be returned.
  • * */ RETURN_DEFAULT; diff --git a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java index 9e06e723b6..0ab30d5271 100644 --- a/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java +++ b/core/src/main/java/org/mapstruct/NullValuePropertyMappingStrategy.java @@ -10,7 +10,7 @@ * {@link NullValuePropertyMappingStrategy} can be defined on {@link MapperConfig}, {@link Mapper}, {@link BeanMapping} * and {@link Mapping}. * Precedence is arranged in the reverse order. So {@link Mapping} will override {@link BeanMapping}, will - * overide {@link Mapper} + * override {@link Mapper} * * The enum only applies to update methods: methods that update a pre-existing target (annotated with * {@code @}{@link MappingTarget}). @@ -27,7 +27,9 @@ public enum NullValuePropertyMappingStrategy { /** - * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null}. + * If a source bean property equals {@code null} the target bean property will be set explicitly to {@code null} + * or {@link java.util.Optional#empty() Optional.empty()} in case the target type is an + * {@link java.util.Optional Optional}. */ SET_TO_NULL, @@ -36,6 +38,7 @@ public enum NullValuePropertyMappingStrategy { *

    * This means: *

      + *
    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. @@ -53,5 +56,12 @@ public enum NullValuePropertyMappingStrategy { * If a source bean property equals {@code null} the target bean property will be ignored and retain its * existing value. */ - IGNORE; + IGNORE, + + /** + * If a source bean property equals {@code null} the target bean property will be cleared. + * This strategy is only applicable to target properties that are of type {@link java.util.Collection Collection} + * or {@link java.util.Map Map}. + */ + CLEAR; } diff --git a/core/src/main/java/org/mapstruct/Qualifier.java b/core/src/main/java/org/mapstruct/Qualifier.java index c21390e8ca..9e18c3e420 100644 --- a/core/src/main/java/org/mapstruct/Qualifier.java +++ b/core/src/main/java/org/mapstruct/Qualifier.java @@ -14,23 +14,70 @@ * 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: + * Can be used in: *

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

      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 EnglishToGerman {
      + * 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}. * 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 index e4064f248a..9d617fec0c 100644 --- a/core/src/main/java/org/mapstruct/TargetType.java +++ b/core/src/main/java/org/mapstruct/TargetType.java @@ -16,6 +16,35 @@ * 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) diff --git a/core/src/main/java/org/mapstruct/ValueMapping.java b/core/src/main/java/org/mapstruct/ValueMapping.java index 26ed661dce..7ad3726e48 100644 --- a/core/src/main/java/org/mapstruct/ValueMapping.java +++ b/core/src/main/java/org/mapstruct/ValueMapping.java @@ -18,18 +18,16 @@ *
        *
      1. Enumeration to Enumeration
      2. *
      - *

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

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

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

      - * 
      + * 
      
        * @ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ),
        * @ValueMapping( source = "STANDARD", target = MappingConstants.NULL ),
        * @ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" )
      @@ -70,11 +64,27 @@
        * +---------------------+----------------------------+
        * 
      * + * Example 3: + * + * MapStruct will WARN on incomplete mappings. However, if for some reason no match is found, an + * {@link java.lang.IllegalStateException} will be thrown. This compile-time error can be avoided by + * using {@link MappingConstants#THROW_EXCEPTION} for {@link ValueMapping#target()}. It will result an + * {@link java.lang.IllegalArgumentException} at runtime. + *
      
      + * @ValueMapping( source = "STANDARD", target = "DEFAULT" ),
      + * @ValueMapping( source = "C2C", target = MappingConstants.THROW_EXCEPTION )
      + * ExternalOrderType orderTypeToExternalOrderType(OrderType orderType);
      + * 
      + * Mapping result:
      + * {@link java.lang.IllegalArgumentException} with the error message:
      + * Unexpected enum constant: C2C
      + * 
      + * * @author Sjaak Derksen */ @Repeatable(ValueMappings.class) @Retention(RetentionPolicy.CLASS) -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) public @interface ValueMapping { /** * The source value constant to use for this mapping. @@ -104,6 +114,7 @@ *
        *
      1. enum constant name
      2. *
      3. {@link MappingConstants#NULL}
      4. + *
      5. {@link MappingConstants#THROW_EXCEPTION}
      6. *
      * * @return The target value. diff --git a/core/src/main/java/org/mapstruct/ValueMappings.java b/core/src/main/java/org/mapstruct/ValueMappings.java index f2450a2fd6..95d4c7d5ce 100644 --- a/core/src/main/java/org/mapstruct/ValueMappings.java +++ b/core/src/main/java/org/mapstruct/ValueMappings.java @@ -12,13 +12,43 @@ /** * Constructs a set of value (constant) mappings. + *

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

      These two examples are equal

      + *
      
      + * // before Java 8
      + * @Mapper
      + * public interface GenderMapper {
      + *     @ValueMappings({
      + *         @ValueMapping(target = "M", source = "MALE"),
      + *         @ValueMapping(target = "F", source = "FEMALE")
      + *     })
      + *     GenderDto mapToDto(Gender gender);
      + * }
      + * 
      + *
      
      + * //Java 8 and later
      + * @Mapper
      + * public interface GenderMapper {
      + *     @ValueMapping(target = "M", source = "MALE"),
      + *     @ValueMapping(target = "F", source = "FEMALE")
      + *     GenderDto mapToDto(Gender gender);
      + * }
      + * 
      * * @author Sjaak Derksen */ -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.CLASS) public @interface ValueMappings { + /** + * The value mappings that should be applied. + * + * @return the value mappings + */ ValueMapping[] value(); } diff --git a/core/src/main/java/org/mapstruct/control/DeepClone.java b/core/src/main/java/org/mapstruct/control/DeepClone.java new file mode 100644 index 0000000000..a5264c861e --- /dev/null +++ b/core/src/main/java/org/mapstruct/control/DeepClone.java @@ -0,0 +1,24 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.control; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.util.Experimental; + +/** + * Clones a source type to a target type (assuming source and target are of the same type). + * + * @author Sjaak Derksen + * + * @since 1.4 + */ +@Retention(RetentionPolicy.CLASS) +@Experimental +@MappingControl( MappingControl.Use.MAPPING_METHOD ) +public @interface DeepClone { +} diff --git a/core/src/main/java/org/mapstruct/control/MappingControl.java b/core/src/main/java/org/mapstruct/control/MappingControl.java new file mode 100644 index 0000000000..1cb5bf2bb1 --- /dev/null +++ b/core/src/main/java/org/mapstruct/control/MappingControl.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.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/package-info.java b/core/src/main/java/org/mapstruct/package-info.java index 02f2b31a42..5a53b5a6d3 100644 --- a/core/src/main/java/org/mapstruct/package-info.java +++ b/core/src/main/java/org/mapstruct/package-info.java @@ -13,6 +13,6 @@ * This package contains several annotations which allow to configure how mapper interfaces are generated. *

      * - * @see MapStruct reference documentation + * @see MapStruct reference documentation */ package org.mapstruct; diff --git a/core/src/main/java/org/mapstruct/util/Experimental.java b/core/src/main/java/org/mapstruct/util/Experimental.java index a945a87d7c..5fd5eb3b5c 100644 --- a/core/src/main/java/org/mapstruct/util/Experimental.java +++ b/core/src/main/java/org/mapstruct/util/Experimental.java @@ -17,5 +17,11 @@ @Documented @Retention(RetentionPolicy.SOURCE) public @interface Experimental { + + /** + * The reason why the feature is considered experimental. + * + * @return the reason why the feature is considered experimental. + */ String value() default ""; } diff --git a/core/src/test/java/org/mapstruct/factory/MappersTest.java b/core/src/test/java/org/mapstruct/factory/MappersTest.java index 60371cada8..6118ba09cb 100644 --- a/core/src/test/java/org/mapstruct/factory/MappersTest.java +++ b/core/src/test/java/org/mapstruct/factory/MappersTest.java @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.test.model.Foo; import org.mapstruct.test.model.SomeClass; diff --git a/distribution/pom.xml b/distribution/pom.xml index b404ed3cee..b480e24a06 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.4.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -34,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 + @@ -54,7 +71,7 @@ org.freemarker freemarker ${project.build.directory}/freemarker-unpacked - META-INF/LICENSE.txt,META-INF/NOTICE.txt + META-INF/LICENSE @@ -88,7 +105,7 @@ MapStruct ${project.version} MapStruct ${project.version} - MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0.]]> + MapStruct Authors; All rights reserved. Released under the Apache Software License 2.0.]]> @@ -96,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 @@ -116,6 +131,23 @@ true true true + + + + + if (typeof useModuleDirectories !== 'undefined') { + useModuleDirectories = false; + } + + ]]> + + --allow-script-in-comments + @@ -135,7 +167,7 @@ ${basedir}/src/main/assembly/dist.xml mapstruct-${project.version} - gnu + posix @@ -156,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 f519e5fc9c..e0bc00496b 100644 --- a/distribution/src/main/assembly/dist.xml +++ b/distribution/src/main/assembly/dist.xml @@ -42,11 +42,7 @@ / - target/freemarker-unpacked/META-INF/NOTICE.txt - / - - - target/freemarker-unpacked/META-INF/LICENSE.txt + target/freemarker-unpacked/META-INF/LICENSE etc/freemarker @@ -80,7 +76,7 @@ - target/site/apidocs + target/reports/apidocs docs/api diff --git a/documentation/pom.xml b/documentation/pom.xml index 90c3e0b64e..21a2b151bd 100644 --- a/documentation/pom.xml +++ b/documentation/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.4.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -21,9 +21,9 @@ MapStruct Documentation - 1.5.0-alpha.11 - 1.5.4 - 1.7.21 + 1.6.0 + 2.5.1 + 9.2.17.0 @@ -33,7 +33,7 @@ org.asciidoctor asciidoctor-maven-plugin - 1.5.3 + 2.1.0 org.asciidoctor @@ -52,7 +52,6 @@ - coderay mapstruct-reference-guide.asciidoc ${project.version} 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..34f1e19817 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -0,0 +1,624 @@ +== 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`. + +[[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..86ed345630 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-2-set-up.asciidoc @@ -0,0 +1,384 @@ +[[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.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..35afc93a51 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc @@ -0,0 +1,133 @@ +[[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. +==== \ No newline at end of file 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..9da7ff75c1 --- /dev/null +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -0,0 +1,831 @@ +[[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.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.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/controlling-nested-bean-mappings.asciidoc b/documentation/src/main/asciidoc/controlling-nested-bean-mappings.asciidoc deleted file mode 100644 index a3e6cdb82e..0000000000 --- a/documentation/src/main/asciidoc/controlling-nested-bean-mappings.asciidoc +++ /dev/null @@ -1,89 +0,0 @@ -[[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. -==== - -[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. -==== diff --git a/documentation/src/main/asciidoc/mapping-streams.asciidoc b/documentation/src/main/asciidoc/mapping-streams.asciidoc deleted file mode 100644 index ccc3e56854..0000000000 --- a/documentation/src/main/asciidoc/mapping-streams.asciidoc +++ /dev/null @@ -1,67 +0,0 @@ -[[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( HashSet::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/mapstruct-reference-guide.asciidoc b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc index 467dc910c1..1705ed4af1 100644 --- a/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc +++ b/documentation/src/main/asciidoc/mapstruct-reference-guide.asciidoc @@ -2,6 +2,7 @@ :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 @@ -11,7 +12,7 @@ [[Preface]] == Preface This is the reference documentation of MapStruct, an annotation processor for generating type-safe, performant and dependency-free bean mapping code. -This guide covers all the functionality provided by MapStruct. In case this guide doesn't answer all your questions just join the MapStruct https://groups.google.com/forum/?fromgroups#!forum/mapstruct-users[Google group] to get help. +This guide covers all the functionality provided by MapStruct. In case this guide doesn't answer all your questions just join the MapStruct https://github.com/mapstruct/mapstruct/discussions[GitHub Discussions] to get help. You found a typo or other error in this guide? Please let us know by opening an issue in the https://github.com/mapstruct/mapstruct[MapStruct GitHub repository], or, better yet, help the community and send a pull request for fixing it! @@ -20,2888 +21,30 @@ This work is licensed under the http://creativecommons.org/licenses/by-sa/4.0/[C :numbered: -[[introduction]] -== Introduction +include::chapter-1-introduction.asciidoc[] -MapStruct is a Java http://docs.oracle.com/javase/6/docs/technotes/guides/apt/index.html[annotation processor] for the generation of type-safe bean mapping classes. +include::chapter-2-set-up.asciidoc[] -All you have to do is to define a mapper interface which declares any required mapping methods. During compilation, MapStruct will generate an implementation of this interface. This implementation uses plain Java method invocations for mapping between source and target objects, i.e. no reflection or similar. +include::chapter-3-defining-a-mapper.asciidoc[] -Compared to writing mapping code from hand, MapStruct saves time by generating code which is tedious and error-prone to write. Following a convention over configuration approach, MapStruct uses sensible defaults but steps out of your way when it comes to configuring or implementing special behavior. +include::chapter-4-retrieving-a-mapper.asciidoc[] -Compared to dynamic mapping frameworks, MapStruct offers the following advantages: +include::chapter-5-data-type-conversions.asciidoc[] -* Fast execution by using plain method invocations instead of reflection -* Compile-time type safety: Only objects and attributes mapping to each other can be mapped, no accidental mapping of an order entity into a customer DTO etc. -* Clear error-reports at build time, if - ** mappings are incomplete (not all target properties are mapped) - ** mappings are incorrect (cannot find a proper mapping method or type conversion) +include::chapter-6-mapping-collections.asciidoc[] -[[setup]] -== Set up +include::chapter-7-mapping-streams.asciidoc[] -MapStruct is a Java annotation processor based on http://www.jcp.org/en/jsr/detail?id=269[JSR 269] and as such can be used within command line builds (javac, Ant, Maven etc.) as well as from within your IDE. +include::chapter-8-mapping-values.asciidoc[] -It comprises the following artifacts: +include::chapter-9-object-factories.asciidoc[] -* _org.mapstruct:mapstruct_: contains the required annotations such as `@Mapping` -* _org.mapstruct:mapstruct-processor_: contains the annotation processor which generates mapper implementations +include::chapter-10-advanced-mapping-options.asciidoc[] -=== Apache Maven +include::chapter-11-reusing-mapping-configurations.asciidoc[] -For Maven based projects add the following to your POM file in order to use MapStruct: +include::chapter-12-customizing-mapping.asciidoc[] -.Maven configuration -==== -[source, xml, linenums] -[subs="verbatim,attributes"] ----- -... - - {mapstructVersion} - -... - - - org.mapstruct - mapstruct - ${org.mapstruct.version} - - -... - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - - - org.mapstruct - mapstruct-processor - ${org.mapstruct.version} - - - - - - -... ----- -==== +include::chapter-13-using-mapstruct-spi.asciidoc[] -[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 (3.4 and later) -==== -[source, groovy, linenums] -[subs="verbatim,attributes"] ----- -... -plugins { - ... - id 'net.ltgt.apt' version '0.20' -} - -// You can integrate with your IDEs. -// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides -apply plugin: 'net.ltgt.apt-idea' -apply plugin: 'net.ltgt.apt-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}" -} -... ----- -==== -.Gradle (3.3 and older) -==== -[source, groovy, linenums] -[subs="verbatim,attributes"] ----- -... -plugins { - ... - id 'net.ltgt.apt' version '0.20' -} - -// You can integrate with your IDEs. -// See more details: https://github.com/tbroyer/gradle-apt-plugin#usage-with-ides -apply plugin: 'net.ltgt.apt-idea' -apply plugin: 'net.ltgt.apt-eclipse' - -dependencies { - ... - compile "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 an `options` element within the configuration of the Maven processor plug-in like this: - -.Maven configuration -==== -[source, xml, linenums] -[subs="verbatim,attributes"] ----- -... - - org.apache.maven.plugins - maven-compiler-plugin - 3.5.1 - - 1.8 - 1.8 - - - org.mapstruct - mapstruct-processor - ${org.mapstruct.version} - - - - 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 CDI bean and can be retrieved via `@Inject` -* `spring`: the generated mapper is a singleton-scoped Spring bean and can be retrieved via `@Autowired` -* `jsr330`: the generated mapper is annotated with {@code @Named} and can be retrieved via `@Inject`, e.g. using Spring - -If a component model is given for a specific mapper via `@Mapper#componentModel()`, the value from the annotation takes precedence. -|`default` - -|`mapstruct.unmappedTargetPolicy` -|The default reporting policy to be applied in case an attribute of the target object of a mapping method is not populated with a source value. - -Supported values are: - -* `ERROR`: any unmapped target property will cause the mapping code generation to fail -* `WARN`: any unmapped target property will cause a warning at build time -* `IGNORE`: unmapped target properties are ignored - -If a policy is given for a specific mapper via `@Mapper#unmappedTargetPolicy()`, the value from the annotation takes precedence. -|`WARN` -|=== - -=== Using MapStruct on Java 9 - -MapStruct can be used with Java 9 (JPMS), support for it is experimental. - -A core theme of Java 9 is the modularization of the JDK. One effect of this is that a specific module needs to be enabled for a project in order to use the `javax.annotation.Generated` annotation. `@Generated` is added by MapStruct to generated mapper classes to tag them as generated code, stating the date of generation, the generator version etc. - -To allow usage of the `@Generated` annotation the module _java.xml.ws.annotation_ must be enabled. When using Maven, this can be done like this: - - export MAVEN_OPTS="--add-modules java.xml.ws.annotation" - -If the `@Generated` annotation is not available, MapStruct will detect this situation and not add it to generated mappers. - -[NOTE] -===== -In Java 9 `java.annotation.processing.Generated` was added (part of the `java.compiler` module), -if this annotation is available then it will be used. -===== - -[[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(source = "make", target = "manufacturer") - @Mapping(source = "numberOfSeats", target = "seatCount") - CarDto carToCarDto(Car car); - - @Mapping(source = "name", target = "fullName") - PersonDto personToPersonDto(Person person); -} ----- -==== - -The `@Mapper` annotation causes the MapStruct code generator to create an implementation of the `CarMapper` interface during build-time. - -In the generated method implementations all readable properties from the source type (e.g. `Car`) will be copied into the corresponding property in the target type (e.g. `CarDto`): - -* 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 have to be specified by means of the `@Mapping` and no warnings will be issued on missing target properties. -==== -[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. - -[[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(source = "person.description", target = "description") - @Mapping(source = "address.houseNo", target = "houseNumber") - DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address); -} ----- -==== - -The shown mapping method takes two source parameters and returns a combined target object. As with single-parameter mapping methods properties are mapped by name. - -In case several source objects define a property with the same name, the source parameter from which to retrieve the property must be specified using the `@Mapping` annotation as shown for the `description` property in the example. An error will be raised when such an ambiguity is not resolved. For properties which only exist once in the given source objects it is optional to specify the source parameter's name as it can be determined automatically. - -[WARNING] -==== -Specifying the parameter in which the property resides is mandatory when using the `@Mapping` annotation. -==== - -[TIP] -==== -Mapping methods with several source parameters will return `null` in case all the source parameters are `null`. Otherwise the target object will be instantiated and all properties from the provided parameters will be propagated. -==== - -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(source = "person.description", target = "description") - @Mapping(source = "hn", target = "houseNumber") - 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`. - -[[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(source = "customerName", target = "name") - Customer toCustomer(CustomerDto customerDto); - - @InheritInverseConfiguration - CustomerDto fromCustomer(Customer customer); -} ----- -==== - -For the configuration from above, the generated mapper looks like: - -.Generated mapper for example classes -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -// GENERATED CODE -public class CustomerMapperImpl implements CustomerMapper { - - @Override - public Customer toCustomer(CustomerDto customerDto) { - // ... - customer.setId( customerDto.id ); - customer.setName( customerDto.customerName ); - // ... - } - - @Override - public CustomerDto fromCustomer(Customer customer) { - // ... - customerDto.id = customer.getId(); - customerDto.customerName = customer.getName(); - // ... - } -} ----- -==== - -You can find the complete example in the -https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-field-mapping[mapstruct-examples-field-mapping] -project on GitHub. - -[[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 a parameterless public static builder creation method that returns a builder. -So for example `Person` has a public static method that returns `PersonBuilder`. -* The builder type has a parameterless public method (build method) that returns the type being build -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] -====== -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. -====== - -.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] - requires having the Lombok classes in a separate module. See for more information https://github.com/rzwitserloot/lombok/issues/1538[rzwitserloot/lombok#1538] -* 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 use the `NoOpBuilderProvider` by creating a `org.mapstruct.ap.spi.BuilderProvider` file in the `META-INF/services` directory with `org.mapstruct.ap.spi.NoOpBuilderProvider` as it's content. -==== - -[[retrieving-mapper]] -== Retrieving a mapper - -[[mappers-factory]] -=== The Mappers factory - -Mapper instances can be retrieved via the `org.mapstruct.factory.Mappers` class. Just invoke the `getMapper()` method, passing the interface type of the mapper to return: - -.Using the Mappers factory -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -CarMapper mapper = Mappers.getMapper( CarMapper.class ); ----- -==== - -By convention, a mapper interface should define a member called `INSTANCE` which holds a single instance of the mapper type: - -.Declaring an instance of a mapper -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); - - CarDto carToCarDto(Car car); -} - ----- -==== - -This pattern makes it very easy for clients to use mapper objects without repeatedly instantiating new instances: - -.Accessing a mapper -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -Car car = ...; -CarDto dto = CarMapper.INSTANCE.carToCarDto( car ); ----- -==== - -Note that mappers generated by MapStruct are thread-safe and thus can safely be accessed from several threads at the same time. - -[[using-dependency-injection]] -=== Using dependency injection - -If you're working with a dependency injection framework such as http://jcp.org/en/jsr/detail?id=346[CDI] (Contexts and Dependency Injection for Java^TM^ EE) or the http://www.springsource.org/spring-framework[Spring Framework], it is recommended to obtain mapper objects via dependency injection as well. For that purpose you can specify the component model which generated mapper classes should be based on either via `@Mapper#componentModel` or using a processor option as described in <>. - -Currently there is support for CDI and Spring (the 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. In both cases the required annotations will be added to the generated mapper implementations classes in order to make the same subject to dependency injection. The following shows an example using CDI: - -.A mapper using the CDI component model -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(componentModel = "cdi") -public interface CarMapper { - - CarDto carToCarDto(Car car); -} - ----- -==== - -The generated mapper implementation will be marked with the `@ApplicationScoped` annotation and thus can be injected into fields, constructor arguments etc. using the `@Inject` annotation: - -.Obtaining a mapper via dependency injection -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Inject -private CarMapper mapper; ----- -==== - -A mapper which uses other mapper classes (see <>) will obtain these mappers using the configured component model. So if `CarMapper` from the previous example was using another mapper, this other mapper would have to be an injectable CDI bean as well. - -[[injection-strategy]] -=== Injection strategy - -When using <>, you can choose between field and constructor 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 = "cdi", uses = EngineMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR) -public interface CarMapper { - CarDto carToCarDto(Car car); -} ----- -==== - -The generated mapper will inject all classes defined in the **uses** attribute. -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. -For now, the default injection strategy is field injection. -It is recommended to use constructor injection to simplify testing. - -[TIP] -==== -For abstract classes or decorators setter injection should be used. -==== - -[[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 big number types (`java.math.BigInteger`, `java.math.BigDecimal`) and Java primitive types (including their wrappers) as well as String. A format string as understood by `java.text.DecimalFormat` can be specified. - -.Conversion from BigDecimal to String -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(source = "power", numberFormat = "#.##E0") - CarDto carToCarDto(Car car); - -} ----- -==== - - -* Between `JAXBElement` and `T`, `List>` and `List` - -* Between `java.util.Calendar`/`java.util.Date` and JAXB's `XMLGregorianCalendar` - -* Between `java.util.Date`/`XMLGregorianCalendar` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option as this: - -.Conversion from Date to String -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(source = "manufacturingDate", dateFormat = "dd.MM.yyyy") - CarDto carToCarDto(Car car); - - @IterableMapping(dateFormat = "dd.MM.yyyy") - List stringListToDateList(List dates); -} ----- -==== - -* Between Jodas `org.joda.time.DateTime`, `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate`, `org.joda.time.LocalTime` and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above). - -* Between Jodas `org.joda.time.DateTime` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Calendar`. - -* Between Jodas `org.joda.time.LocalDateTime`, `org.joda.time.LocalDate` and `javax.xml.datatype.XMLGregorianCalendar`, `java.util.Date`. - -* Between `java.time.ZonedDateTime`, `java.time.LocalDateTime`, `java.time.LocalDate`, `java.time.LocalTime` from Java 8 Date-Time package and `String`. A format string as understood by `java.text.SimpleDateFormat` can be specified via the `dateFormat` option (see above). - -* Between `java.time.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.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 - -[[mapping-object-references]] -=== Mapping object references - -Typically an object has not only primitive attributes but also references other objects. E.g. the `Car` class could contain a reference to a `Person` object (representing the car's driver) which should be mapped to a `PersonDto` object referenced by the `CarDto` class. - -In this case just define a mapping method for the referenced object type as well: - -.Mapper with one mapping method using another -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - CarDto carToCarDto(Car car); - - PersonDto personToPersonDto(Person person); -} ----- -==== - -The generated code for the `carToCarDto()` method will invoke the `personToPersonDto()` method for mapping the `driver` attribute, while the generated implementation for `personToPersonDto()` performs the mapping of person objects. - -That way it is possible to map arbitrary deep object graphs. When mapping from entities into data transfer objects it is often useful to cut references to other entities at a certain point. To do so, implement a custom mapping method (see the next section) which e.g. maps a referenced entity to its id in the target object. - -When generating the implementation of a mapping method, MapStruct will apply the following routine for each attribute pair in the source and target object: - -* If source and target attribute have the same type, the value will be simply copied from source to target. If the attribute is a collection (e.g. a `List`) a copy of the collection will be set into the target attribute. -* If source and target attribute type differ, check whether there is 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. - -[NOTE] -==== -In order to stop MapStruct from generating automatic sub-mapping methods, one can use `@Mapper( disableSubMappingMethodsGeneration = true )`. -==== - -[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. -==== - -include::controlling-nested-bean-mappings.asciidoc[] - -[[invoking-other-mappers]] -=== Invoking other mappers - -In addition to methods defined on the same mapper type MapStruct can also invoke mapping methods defined in other classes, be it mappers generated by MapStruct or hand-written mapping methods. This can be useful to structure your mapping code in several classes (e.g. with one mapper type per application module) or if you want to provide custom mapping logic which can't be generated by MapStruct. - -For instance the `Car` class might contain an attribute `manufacturingDate` while the corresponding DTO attribute is of type String. In order to map this attribute, you could implement a mapper class like this: - -.Manually implemented mapper class -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class DateMapper { - - public String asString(Date date) { - return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ) - .format( date ) : null; - } - - public Date asDate(String date) { - try { - return date != null ? new SimpleDateFormat( "yyyy-MM-dd" ) - .parse( date ) : null; - } - catch ( ParseException e ) { - throw new RuntimeException( e ); - } - } -} ----- -==== - -In the `@Mapper` annotation at the `CarMapper` interface reference the `DateMapper` class like this: - -.Referencing another mapper class -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(uses=DateMapper.class) -public class CarMapper { - - CarDto carToCarDto(Car car); -} ----- -==== - -When generating code for the implementation of the `carToCarDto()` method, MapStruct will look for a method which maps a `Date` object into a String, find it on the `DateMapper` class and generate an invocation of `asString()` for mapping the `manufacturingDate` attribute. - -Generated mappers retrieve referenced mappers using the component model configured for them. If e.g. CDI was used as component model for `CarMapper`, `DateMapper` would have to be a CDI bean as well. When using the default component model, any hand-written mapper classes to be referenced by MapStruct generated mappers must declare a public no-args constructor in order to be instantiable. - -[[passing-target-type]] -=== Passing the mapping target type to custom mappers - -When having a custom mapper hooked into the generated mapper with `@Mapper#uses()`, an additional parameter of type `Class` (or a super-type of it) can be defined in the custom mapping method in order to perform general mapping tasks for specific target object types. That attribute must be annotated with `@TargetType` for MapStruct to generate calls that pass the `Class` instance representing the corresponding property type of the target bean. - -For instance, the `CarDto` could have a property `owner` of type `Reference` that contains the primary key of a `Person` entity. You could now create a generic custom mapper that resolves any `Reference` objects to their corresponding managed JPA entity instances. - -.Mapping method expecting mapping target type as parameter -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@ApplicationScoped // CDI component model -public class ReferenceMapper { - - @PersistenceContext - private EntityManager entityManager; - - public T resolve(Reference reference, @TargetType Class entityClass) { - return reference != null ? entityManager.find( entityClass, reference.getPk() ) : null; - } - - public Reference toReference(BaseEntity entity) { - return entity != null ? new Reference( entity.getPk() ) : null; - } -} - -@Mapper(componentModel = "cdi", uses = ReferenceMapper.class ) -public interface CarMapper { - - Car carDtoToCar(CarDto carDto); -} ----- -==== - -MapStruct will then generate something like this: - -.Generated code -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -@ApplicationScoped -public class CarMapperImpl implements CarMapper { - - @Inject - private ReferenceMapper referenceMapper; - - @Override - public Car carDtoToCar(CarDto carDto) { - if ( carDto == null ) { - return null; - } - - Car car = new Car(); - - car.setOwner( referenceMapper.resolve( carDto.getOwner(), Owner.class ) ); - // ... - - return car; - } -} ----- -==== - -[[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 retention `TitleTranslator` on class level, `EnglishToGerman`, `GermanToEnglish` on method level! - -Then, using the qualifiers, the mapping could look like this: - -.Mapper using qualifiers -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper( uses = Titles.class ) -public interface MovieMapper { - - @Mapping( target = "title", qualifiedBy = { TitleTranslator.class, EnglishToGerman.class } ) - GermanRelease toGerman( OriginalRelease movies ); - -} ----- -==== - -.Custom mapper qualifying the methods it provides -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@TitleTranslator -public class Titles { - - @EnglishToGerman - public String translateTitleEG(String title) { - // some mapping logic - } - - @GermanToEnglish - public String translateTitleGE(String title) { - // some mapping logic - } -} ----- -==== - -[WARNING] -==== -Please make sure the used retention policy equals retention policy `CLASS` (`@Retention(RetentionPolicy.CLASS)`). -==== - -[WARNING] -==== -A class / method annotated with a qualifier will not qualify anymore for mappings that do not have the `qualifiedBy` element. -==== - -[TIP] -==== -The same mechanism is also present on bean mappings: `@BeanMapping#qualifiedBy`: it selects the factory method marked with the indicated qualifier. -==== - -In many occasions, declaring a new annotation to aid the selection process can be too much for what you try to achieve. For those situations, MapStruct has the `@Named` annotation. This annotation is a pre-defined qualifier (annotated with `@Qualifier` itself) and can be used to name a Mapper or, more directly a mapping method by means of its value. The same example above would look like: - -.Custom mapper, annotating the methods to qualify by means of `@Named` -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Named("TitleTranslator") -public class Titles { - - @Named("EnglishToGerman") - public String translateTitleEG(String title) { - // some mapping logic - } - - @Named("GermanToEnglish") - public String translateTitleGE(String title) { - // some mapping logic - } -} ----- -==== - -.Mapper using named -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper( uses = Titles.class ) -public interface MovieMapper { - - @Mapping( target = "title", qualifiedByName = { "TitleTranslator", "EnglishToGerman" } ) - GermanRelease toGerman( OriginalRelease movies ); - -} ----- -==== - -[WARNING] -==== -Although the used mechanism is the same, the user has to be a bit more careful. Refactoring the name of a defined qualifier in an IDE will neatly refactor all other occurrences as well. This is obviously not the case for changing a name. -==== - - -[[mapping-collections]] -== Mapping collections - -The mapping of collection types (`List`, `Set` etc.) is done in the same way as mapping bean types, i.e. by defining mapping methods with the required source and target types in a mapper interface. MapStruct supports a wide range of iterable types from the http://docs.oracle.com/javase/tutorial/collections/intro/index.html[Java Collection Framework]. - -The generated code will contain a loop which iterates over the source collection, converts each element and puts it into the target collection. If a mapping method for the collection element types is found in the given mapper or the mapper it uses, this method is invoked to perform the element conversion. Alternatively, if an implicit conversion for the source and target element types exists, this conversion routine will be invoked. The following shows an example: - -.Mapper with collection mapping methods -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - Set integerSetToStringSet(Set integers); - - List carsToCarDtos(List cars); - - CarDto carToCarDto(Car car); -} ----- -==== - -The generated implementation of the `integerSetToStringSet` performs the conversion from `Integer` to `String` for each element, while the generated `carsToCarDtos()` method invokes the `carToCarDto()` method for each contained element as shown in the following: - -.Generated collection mapping methods -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -@Override -public Set integerSetToStringSet(Set integers) { - if ( integers == null ) { - return null; - } - - Set set = new HashSet(); - - for ( Integer integer : integers ) { - set.add( String.valueOf( integer ) ); - } - - return set; -} - -@Override -public List carsToCarDtos(List cars) { - if ( cars == null ) { - return null; - } - - List list = new ArrayList(); - - for ( Car car : cars ) { - list.add( carToCarDto( car ) ); - } - - return list; -} ----- -==== - -Note that MapStruct will look for a collection mapping method with matching parameter and return type, when mapping a collection-typed attribute of a bean, e.g. from `Car#passengers` (of type `List`) to `CarDto#passengers` (of type `List`). - -.Usage of collection mapping method to map a bean property -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -carDto.setPassengers( personsToPersonDtos( car.getPassengers() ) ); -... ----- -==== - -Some frameworks and libraries only expose JavaBeans getters but no setters for collection-typed properties. Types generated from an XML schema using JAXB adhere to this pattern by default. In this case the generated code for mapping such a property invokes its getter and adds all the mapped elements: - -.Usage of an adding method for collection mapping -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -carDto.getPassengers().addAll( personsToPersonDtos( car.getPassengers() ) ); -... ----- -==== - -[WARNING] -==== -It is not allowed to declare mapping methods with an iterable source and a non-iterable target or the other way around. An error will be raised when detecting this situation. -==== - -[[mapping-maps]] -=== Mapping maps - -Also map-based mapping methods are supported. The following shows an example: - -.Map mapping method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public interface SourceTargetMapper { - - @MapMapping(valueDateFormat = "dd.MM.yyyy") - Map longDateMapToStringStringMap(Map source); -} ----- -==== - -Similar to iterable mappings, the generated code will iterate through the source map, convert each value and key (either by means of an implicit conversion or by invoking another mapping method) and put them into the target map: - -.Generated implementation of map mapping method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -//GENERATED CODE -@Override -public Map stringStringMapToLongDateMap(Map source) { - if ( source == null ) { - return null; - } - - Map map = new HashMap(); - - for ( Map.Entry entry : source.entrySet() ) { - - Long key = Long.parseLong( entry.getKey() ); - Date value; - try { - value = new SimpleDateFormat( "dd.MM.yyyy" ).parse( entry.getValue() ); - } - catch( ParseException e ) { - throw new RuntimeException( e ); - } - - map.put( key, value ); - } - - return map; -} ----- -==== - -[[collection-mapping-strategies]] -=== Collection mapping strategies - -MapStruct has a `CollectionMappingStrategy`, with the possible values: `ACCESSOR_ONLY`, `SETTER_PREFERRED`, `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/absense of a `set-s`, `add-` and / or `get-s` method on the target object: - -.Collection mapping strategy options -|=== -|Option|Only target set-s Available|Only target add- Available|Both set-s / add- Available|No set-s / add- Available|Existing Target(`@TargetType`) - -|`ACCESSOR_ONLY` -|set-s -|get-s -|set-s -|get-s -|get-s - -|`SETTER_PREFERRED` -|set-s -|add- -|set-s -|get-s -|get-s - -|`ADDER_PREFERRED` -|set-s -|add- -|add- -|get-s -|get-s - -|`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 intialized 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`|`HashSet` - -|`SortedSet`|`TreeSet` - -|`NavigableSet`|`TreeSet` - -|`Map`|`HashMap` - -|`SortedMap`|`TreeMap` - -|`NavigableMap`|`TreeMap` - -|`ConcurrentMap`|`ConcurrentHashMap` -|`ConcurrentNavigableMap`|`ConcurrentSkipListMap` -|=== - -include::mapping-streams.asciidoc[] - -[[mapping-enum-types]] -== Mapping Values - -=== Mapping enum types - -MapStruct supports the generation of methods which map one Java enum type into another. - -By default, each constant from the source enum is mapped to a constant with the same name in the target enum type. If required, a constant from the source enum may be mapped to a constant with another name with help of the `@ValueMapping` annotation. Several constants from the source enum can be mapped to the same constant in the target type. - -The following shows an example: - -.Enum mapping method -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface OrderMapper { - - OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class ); - - @ValueMappings({ - @ValueMapping(source = "EXTRA", target = "SPECIAL"), - @ValueMapping(source = "STANDARD", target = "DEFAULT"), - @ValueMapping(source = "NORMAL", target = "DEFAULT") - }) - ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); -} ----- -==== - -.Enum mapping method result -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -// GENERATED CODE -public class OrderMapperImpl implements OrderMapper { - - @Override - public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { - if ( orderType == null ) { - return null; - } - - ExternalOrderType externalOrderType_; - - switch ( orderType ) { - case EXTRA: externalOrderType_ = ExternalOrderType.SPECIAL; - break; - case STANDARD: externalOrderType_ = ExternalOrderType.DEFAULT; - break; - case NORMAL: externalOrderType_ = ExternalOrderType.DEFAULT; - break; - case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL; - break; - case B2B: externalOrderType_ = ExternalOrderType.B2B; - break; - default: throw new IllegalArgumentException( "Unexpected enum constant: " + orderType ); - } - - return externalOrderType_; - } -} ----- -==== -By default an error will be raised by MapStruct in case a constant of the source enum type does not have a corresponding constant with the same name in the target type and also is not mapped to another constant via `@ValueMapping`. This ensures that all constants are mapped in a safe and predictable manner. The generated -mapping method will throw an IllegalStateException if for some reason an unrecognized source value occurs. - -MapStruct also has a mechanism for mapping any remaining (unspecified) mappings to a default. This can be used only once in a set of value mappings. It comes in two flavors: `` and ``. - -In case of source `` MapStruct will continue to map a source enum constant to a target enum constant with the same name. The remainder of the source enum constants will be mapped to the target specified in the `@ValueMapping` with `` source. - -MapStruct will *not* attempt such name based mapping for `` and directly apply the target specified in the `@ValueMapping` with `` source to the remainder. - -MapStruct is able to handle `null` sources and `null` targets by means of the `` keyword. - -[TIP] -==== -Constants for ``, `` and `` are available in the `MappingConstants` class. -==== - -Finally `@InheritInverseConfiguration` and `@InheritConfiguration` can be used in combination with `@ValueMappings`. - -.Enum mapping method, and -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface SpecialOrderMapper { - - SpecialOrderMapper INSTANCE = Mappers.getMapper( SpecialOrderMapper.class ); - - @ValueMappings({ - @ValueMapping( source = MappingConstants.NULL, target = "DEFAULT" ), - @ValueMapping( source = "STANDARD", target = MappingConstants.NULL ), - @ValueMapping( source = MappingConstants.ANY_REMAINING, target = "SPECIAL" ) - }) - ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); -} ----- -==== - -.Enum mapping method result, and -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -// GENERATED CODE -public class SpecialOrderMapperImpl implements SpecialOrderMapper { - - @Override - public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { - if ( orderType == null ) { - return ExternalOrderType.DEFAULT; - } - - ExternalOrderType externalOrderType_; - - switch ( orderType ) { - case STANDARD: externalOrderType_ = null; - break; - case RETAIL: externalOrderType_ = ExternalOrderType.RETAIL; - break; - case B2B: externalOrderType_ = ExternalOrderType.B2B; - break; - default: externalOrderType_ = ExternalOrderType.SPECIAL; - } - - return externalOrderType_; - } -} ----- -==== - -*Note:* MapStruct would have refrained from mapping the `RETAIL` and `B2B` when `` was used instead of ``. - - -[WARNING] -==== -The mapping of enum to enum via the `@Mapping` annotation is *DEPRECATED*. It will be removed from future versions of MapStruct. Please adapt existing enum mapping methods to make use of `@ValueMapping` instead. -==== - - -[[object-factories]] -== Object factories - -By default, the generated code for mapping one bean type into another 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 - } -} ----- -==== - - -== 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 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 two source properties can be mapped to one target: - -.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 <>). - -[[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 were the `@BeanMapping#resultType` comes in handy. It controls the factory method to select, or in absence of a factory method, the return type to create. - -[TIP] -==== -The same mechanism is present on mapping: `@Mapping#resultType` and works like you expect it would: it selects the mapping method with the desired result type when present. -==== - -[TIP] -==== -The mechanism is also present on iterable mapping and map mapping. `@IterableMapping#elementTargetType` is used to select the mapping method with the desired element in the resulting `Iterable`. For the `@MapMapping` a similar purpose is served by means of `#MapMapping#keyTargetType` and `MapMapping#valueTargetType`. -==== - -[[mapping-result-for-null-arguments]] -=== Controlling mapping result for 'null' arguments - -MapStruct offers control over the object to create when the source argument of the mapping method equals `null`. By default `null` will be returned. - -However, by specifying `nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT` on `@BeanMapping`, `@IterableMapping`, `@MapMapping`, or globally on `@Mapper` or `@MappingConfig`, the mapping result can be altered to return empty *default* values. This means for: - -* *Bean mappings*: an 'empty' target bean will be returned, with the exception of constants and expressions, they will be populated when present. -* *Iterables / Arrays*: an empty iterable will be returned. -* *Maps*: an empty map will be returned. - -The strategy works in a hierarchical fashion. Setting `nullValueMappingStrategy` on mapping method level will override `@Mapper#nullValueMappingStrategy`, and `@Mapper#nullValueMappingStrategy` will override `@MappingConfig#nullValueMappingStrategy`. - - -[[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. - -However: - -1. By specifying `nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT` on `@Mapping`, `@BeanMapping`, `@Mapper` or `@MappingConfig`, the mapping result can be altered to return *default* values. -For `List` MapStruct generates an `ArrayList`, for `Map` a `HashMap`, 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 `@MappingConfig`, the mapping result will be equal to the original value of the `@MappingTarget` annotated target. - -The strategy works in a hierarchical fashion. Setting `Mapping#nullValuePropertyMappingStrategy` on mapping level will override `nullValuePropertyMappingStrategy` on mapping method level will override `@Mapper#nullValuePropertyMappingStrategy`, and `@Mapper#nullValuePropertyMappingStrategy` will override `@MappingConfig#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 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 presense checker returns `not present`. -==== - -[[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. Therefor generated mapping methods will do a null check prior to carrying out mapping on a source property. Handwritten mapping methods must take care of null value checking. They have the possibility to add 'meaning' to `null`. For instance: mapping `null` to a default value. - -The option `nullValueCheckStrategy = 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 `@MappingConfig#nullValueCheckStrategy`. - -[[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 `NullValueheckStrategy` to avoid addition of `null` to the target collection or map. -==== -[[exceptions]] -=== Exceptions - -Calling applications may require handling of exceptions when calling a mapping method. These exceptions could be thrown by hand-written logic and by the generated built-in mapping methods or type-conversions of MapStruct. When the calling application requires handling of exceptions, a throws clause can be defined in the mapping method: - -.Mapper using custom method declaring checked exception -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper(uses = HandWritten.class) -public interface CarMapper { - - CarDto carToCarDto(Car car) throws GearException; -} ----- -==== - -The hand written logic might look like this: - -.Custom mapping method declaring checked exception -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class HandWritten { - - private static final String[] GEAR = {"ONE", "TWO", "THREE", "OVERDRIVE", "REVERSE"}; - - public String toGear(Integer gear) throws GearException, FatalException { - if ( gear == null ) { - throw new FatalException("null is not a valid gear"); - } - - if ( gear < 0 && gear > GEAR.length ) { - throw new GearException("invalid gear"); - } - return GEAR[gear]; - } -} ----- -==== - -MapStruct now, wraps the `FatalException` in a `try-catch` block and rethrows an unchecked `RuntimeException`. MapStruct delegates handling of the `GearException` to the application logic because it is defined as throws clause in the `carToCarDto` method: - -.try-catch block in generated implementation -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -// GENERATED CODE -@Override -public CarDto carToCarDto(Car car) throws GearException { - if ( car == null ) { - return null; - } - - CarDto carDto = new CarDto(); - try { - carDto.setGear( handWritten.toGear( car.getGear() ) ); - } - catch ( FatalException e ) { - throw new RuntimeException( e ); - } - - return carDto; -} ----- -==== - -Some **notes** on null checks. MapStruct does provide null checking only when required: when applying type-conversions or constructing a new type by invoking its constructor. This means that the user is responsible in hand-written code for returning valid non-null objects. Also null objects can be handed to hand-written code, since MapStruct does not want to make assumptions on the meaning assigned by the user to a null object. Hand-written code has to deal with this. - -== Reusing mapping configurations - -This chapter discusses different means of reusing mapping configurations for several mapping methods: "inheritance" of configuration from other methods and sharing central configuration between multiple mapper types. - -[[mapping-configuration-inheritance]] -=== Mapping configuration inheritance - -Method-level configuration annotations such as `@Mapping`, `@BeanMapping`, `@IterableMapping`, etc., can be *inherited* from one mapping method to a *similar* method using the annotation `@InheritConfiguration`: - -.Update method inheriting its configuration -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(target = "numberOfSeats", source = "seatCount") - Car carDtoToCar(CarDto car); - - @InheritConfiguration - void carDtoIntoCar(CarDto carDto, @MappingTarget Car car); -} ----- -==== - -The example above declares a mapping method `carDtoToCar()` with a configuration to define how the property `numberOfSeats` in the type `Car` shall be mapped. The update method that performs the mapping on an existing instance of `Car` needs the same configuration to successfully map all properties. Declaring `@InheritConfiguration` on the method lets MapStruct search for inheritance candidates to apply the annotations of the method that is inherited from. - -One method *A* can inherit the configuration from another method *B* if all types of *A* (source types and result type) are assignable to the corresponding types of *B*. - -Methods that are considered for inheritance need to be defined in the current mapper, a super class/interface, or in the shared configuration interface (as described in <>). - -In case more than one method is applicable as source for the inheritance, the method name must be specified within the annotation: `@InheritConfiguration( name = "carDtoToCar" )`. - -A method can use `@InheritConfiguration` and override or amend the configuration by additionally applying `@Mapping`, `@BeanMapping`, etc. - -[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. - -.Inverse mapping method inheriting its configuration and ignoring some of them -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface CarMapper { - - @Mapping(source = "numberOfSeats", target = "seatCount") - 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`. - -Expressions and constants are excluded (silently ignored) 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] -==== -`@InheritConfiguration` or `@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`. - -== Customizing mappings - -Sometimes it's needed to apply custom logic before or after certain mapping methods. MapStruct provides two ways for doing so: decorators which allow for a type-safe customization of specific mapping methods and the before-mapping and after-mapping lifecycle methods which allow for a generic customization of mapping methods with given source or target types. - -[[customizing-mappers-using-decorators]] -=== Mapping customization with decorators - -In certain cases it may be required to customize a generated mapping method, e.g. to set an additional property in the target object which can't be set by a generated method implementation. MapStruct supports this requirement using decorators. - -[TIP] -When working with the component model `cdi`, use https://docs.jboss.org/cdi/spec/1.0/html/decorators.html[CDI decorators] with MapStruct mappers instead of the `@DecoratedWith` annotation described here. - -To apply a decorator to a mapper class, specify it using the `@DecoratedWith` annotation. - -.Applying a decorator -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -@DecoratedWith(PersonMapperDecorator.class) -public interface PersonMapper { - - PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class ); - - PersonDto personToPersonDto(Person person); - - AddressDto addressToAddressDto(Address address); -} ----- -==== - -The decorator must be a sub-type of the decorated mapper type. You can make it an abstract class which allows to only implement those methods of the mapper interface which you want to customize. For all non-implemented methods, a simple delegation to the original mapper will be generated using the default generation routine. - -The `PersonMapperDecorator` shown below customizes the `personToPersonDto()`. It sets an additional attribute which is not present in the source type of the mapping. The `addressToAddressDto()` method is not customized. - -.Implementing a decorator -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public abstract class PersonMapperDecorator implements PersonMapper { - - private final PersonMapper delegate; - - public PersonMapperDecorator(PersonMapper delegate) { - this.delegate = delegate; - } - - @Override - public PersonDto personToPersonDto(Person person) { - PersonDto dto = delegate.personToPersonDto( person ); - dto.setFullName( person.getFirstName() + " " + person.getLastName() ); - return dto; - } -} ----- -==== - -The example shows how you can optionally inject a delegate with the generated default implementation and use this delegate in your customized decorator methods. - -For a mapper with `componentModel = "default"`, define a constructor with a single parameter which accepts the type of the decorated mapper. - -When working with the component models `spring` or `jsr330`, this needs to be handled differently. - -[[decorators-with-spring]] -==== Decorators with the Spring component model - -When using `@DecoratedWith` on a mapper with component model `spring`, the generated implementation of the original mapper is annotated with the Spring annotation `@Qualifier("delegate")`. To autowire that bean in your decorator, add that qualifier annotation as well: - -.Spring-based decorator -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public abstract class PersonMapperDecorator implements PersonMapper { - - @Autowired - @Qualifier("delegate") - private PersonMapper delegate; - - @Override - public PersonDto personToPersonDto(Person person) { - PersonDto dto = delegate.personToPersonDto( person ); - dto.setName( person.getFirstName() + " " + person.getLastName() ); - - return dto; - } - } ----- -==== - -The generated class that extends the decorator is annotated with Spring's `@Primary` annotation. To autowire the decorated mapper in the application, nothing special needs to be done: - -.Using a decorated mapper -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Autowired -private PersonMapper personMapper; // injects the decorator, with the injected original mapper ----- -==== - -[[decorators-with-jsr-330]] -==== Decorators with the JSR 330 component model - -JSR 330 doesn't specify qualifiers and only allows to specifically name the beans. Hence, the generated implementation of the original mapper is annotated with `@Named("fully-qualified-name-of-generated-implementation")` (please note that when using a decorator, the class name of the mapper implementation ends with an underscore). To inject that bean in your decorator, add the same annotation to the delegate field (e.g. by copy/pasting it from the generated class): - -.JSR 330 based decorator -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public abstract class PersonMapperDecorator implements PersonMapper { - - @Inject - @Named("org.examples.PersonMapperImpl_") - private PersonMapper delegate; - - @Override - public PersonDto personToPersonDto(Person person) { - PersonDto dto = delegate.personToPersonDto( person ); - dto.setName( person.getFirstName() + " " + person.getLastName() ); - - return dto; - } -} ----- -==== - -Unlike with the other component models, the usage site must be aware if a mapper is decorated or not, as for decorated mappers, the parameterless `@Named` annotation must be added to select the decorator to be injected: - -.Using a decorated mapper with JSR 330 -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Inject -@Named -private PersonMapper personMapper; // injects the decorator, with the injected original mapper ----- -==== - -[WARNING] -==== -`@DecoratedWith` in combination with component model `jsr330` is considered experimental as of the 1.0.0.CR2 release. The way the original mapper is referenced in the decorator or the way the decorated mapper is injected in the application code might still change. -==== - -[[customizing-mappings-with-before-and-after]] -=== Mapping customization with before-mapping and after-mapping methods - -Decorators may not always fit the needs when it comes to customizing mappers. For example, if you need to perform the customization not only for a few selected methods, but for all methods that map specific super-types: in that case, you can use *callback methods* that are invoked before the mapping starts or after the mapping finished. - -Callback methods can be implemented in the abstract mapper itself, 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 an `@MappingTarget` parameter are called before any null-checks on source - parameters and constructing a new target bean. -2. `@BeforeMapping` methods with an `@MappingTarget` parameter are called after constructing a new target bean. -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. - - -[[using-spi]] -== Using the MapStruct SPI -=== Custom Accessor Naming Strategy - -MapStruct offers the possibility to override the `AccessorNamingStrategy` via the Service Provide Interface (SPI). A nice example is the use of the fluent API on the source object `GolfPlayer` and `GolfPlayerDto` below. - -.Source object GolfPlayer with fluent API. -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class GolfPlayer { - - private double handicap; - private String name; - - public double handicap() { - return handicap; - } - - public GolfPlayer withHandicap(double handicap) { - this.handicap = handicap; - return this; - } - - public String name() { - return name; - } - - public GolfPlayer withName(String name) { - this.name = name; - return this; - } -} ----- -==== - -.Source object GolfPlayerDto with fluent API. -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -public class GolfPlayerDto { - - private double handicap; - private String name; - - public double handicap() { - return handicap; - } - - public GolfPlayerDto withHandicap(double handicap) { - this.handicap = handicap; - return this; - } - - public String name() { - return name; - } - - public GolfPlayerDto withName(String name) { - this.name = name; - return this - } -} ----- -==== - -We want `GolfPlayer` to be mapped to a target object `GolfPlayerDto` similar like we 'always' do this: - -.Source object with fluent API. -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -@Mapper -public interface GolfPlayerMapper { - - GolfPlayerMapper INSTANCE = Mappers.getMapper( GolfPlayerMapper.class ); - - GolfPlayerDto toDto(GolfPlayer player); - - GolfPlayer toPlayer(GolfPlayerDto player); - -} ----- -==== - -This can be achieved with implementing the SPI `org.mapstruct.ap.spi.AccessorNamingStrategy` as in the following example. Here's an implemented `org.mapstruct.ap.spi.AccessorNamingStrategy`: - -.CustomAccessorNamingStrategy -==== -[source, java, linenums] -[subs="verbatim,attributes"] ----- -/** - * A custom {@link AccessorNamingStrategy} recognizing getters in the form of {@code property()} and setters in the - * form of {@code withProperty(value)}. - */ -public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy { - - @Override - public boolean isGetterMethod(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - return !methodName.startsWith( "with" ) && method.getReturnType().getKind() != TypeKind.VOID; - } - - @Override - public boolean isSetterMethod(ExecutableElement method) { - String methodName = method.getSimpleName().toString(); - return methodName.startsWith( "with" ) && methodName.length() > 4; - } - - @Override - public String getPropertyName(ExecutableElement getterOrSetterMethod) { - String methodName = getterOrSetterMethod.getSimpleName().toString(); - return 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. - -To use a custom SPI implementation, it must be located in a separate JAR file together with the file `META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy` with the fully qualified name of your custom implementation as content (e.g. `org.mapstruct.example.CustomAccessorNamingStrategy`). This JAR file needs to be added to the annotation processor classpath (i.e. add it next to the place where you added the mapstruct-processor jar). - -[TIP] -Fore more details: The example above is present in our examples repository (https://github.com/mapstruct/mapstruct-examples). - -[mapping-exclusion-provider] -=== Mapping Exclusion Provider - -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] ----- -==== - -To use a custom SPI implementation, it must be located in a separate JAR file -together with the file `META-INF/services/org.mapstruct.ap.spi.MappingExclusionProvider` with the fully qualified name of your custom implementation as content -(e.g. `org.mapstruct.example.CustomMappingExclusionProvider`). -This JAR file needs to be added to the annotation processor classpath -(i.e. add it next to the place where you added the mapstruct-processor jar). - - -[[custom-builder-provider]] -=== Custom Builder Provider - -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] ----- -==== +include::chapter-14-third-party-api-integration.asciidoc[] \ No newline at end of file diff --git a/etc/travis-settings.xml b/etc/ci-settings.xml similarity index 100% rename from etc/travis-settings.xml rename to etc/ci-settings.xml diff --git a/etc/toolchains-cloudbees-jenkins.xml b/etc/toolchains-cloudbees-jenkins.xml deleted file mode 100644 index cf7f0262f3..0000000000 --- a/etc/toolchains-cloudbees-jenkins.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - 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 628487ce08..0000000000 --- a/etc/toolchains-example.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - 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/etc/toolchains-travis-jenkins.xml b/etc/toolchains-travis-jenkins.xml deleted file mode 100644 index 0668c72b97..0000000000 --- a/etc/toolchains-travis-jenkins.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - jdk - - 1.8 - oracle - jdk1.8 - - - /usr/lib/jvm/java-8-oracle/ - - - - diff --git a/integrationtest/pom.xml b/integrationtest/pom.xml index 257c32730a..71a61fc2fe 100644 --- a/integrationtest/pom.xml +++ b/integrationtest/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.4.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -24,14 +24,22 @@ ${project.version} true + + + + gradle + https://repo.gradle.org/artifactory/libs-releases/ + + true + + + false + + + - - junit - junit - test - org.assertj assertj-core @@ -42,6 +50,34 @@ maven-verifier test + + org.gradle + gradle-test-kit + 5.6.4 + test + + + org.gradle + gradle-tooling-api + 5.6.4 + test + + + commons-io + commons-io + test + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-engine + test + + @@ -71,19 +107,6 @@ - - org.apache.maven.plugins - maven-checkstyle-plugin - - - check-style - verify - - checkstyle - - - - @@ -97,10 +120,33 @@ javax.xml.bind jaxb-api - 2.3.1 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/AutoValueBuilderTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/AutoValueBuilderTest.java deleted file mode 100644 index 823a530c09..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/AutoValueBuilderTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Filip Hrisafov - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite(baseDir = "autoValueBuilderTest", - processorTypes = ProcessorSuite.ProcessorType.ALL_WITHOUT_PROCESSOR_PLUGIN) -public class AutoValueBuilderTest { -} 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 216d2afdfa..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/CdiTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 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.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/ExternalBeanJarTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/ExternalBeanJarTest.java deleted file mode 100644 index bcfb0c399c..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/ExternalBeanJarTest.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * - * See: https://github.com/mapstruct/mapstruct/issues/1121 - * - * @author Sjaak Derksen - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite(baseDir = "externalbeanjar", processorTypes = ProcessorSuite.ProcessorType.ORACLE_JAVA_8) -public class ExternalBeanJarTest { - -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FreeBuilderBuilderTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FreeBuilderBuilderTest.java deleted file mode 100644 index 250f4ce856..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FreeBuilderBuilderTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Filip Hrisafov - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "freeBuilderBuilderTest", - processorTypes = ProcessorSuite.ProcessorType.ALL_WITHOUT_PROCESSOR_PLUGIN) -public class FreeBuilderBuilderTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java new file mode 100644 index 0000000000..7d397c9499 --- /dev/null +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -0,0 +1,71 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.itest.tests; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.jupiter.api.condition.JRE; +import org.mapstruct.itest.testutil.extension.ProcessorTest; + +/** + * Adds explicit exclusions of test mappers that are known or expected to not work with specific compilers. + * + * @author Andreas Gudian + */ +public final class FullFeatureCompilationExclusionCliEnhancer implements ProcessorTest.CommandLineEnhancer { + @Override + public Collection getAdditionalCommandLineArguments(ProcessorTest.ProcessorType processorType, + JRE currentJreVersion) { + List additionalExcludes = new ArrayList<>(); + + // SPI not working correctly here.. (not picked up) + additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1801/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/bugs/_3089/*.java" ); + + switch ( currentJreVersion ) { + case JAVA_8: + additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); + if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) { + additionalExcludes.add( + "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" ); + } + break; + case JAVA_9: + // TODO find out why this fails: + additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + break; + case JAVA_11: + additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk17/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + break; + case JAVA_17: + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + break; + default: + } + + Collection result = new ArrayList<>(additionalExcludes.size()); + for ( int i = 0; i < additionalExcludes.size(); i++ ) { + result.add( "-DadditionalExclude" + i + "=" + additionalExcludes.get( i ) ); + } + + return result; + } +} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java deleted file mode 100644 index fe3836e306..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.tests.FullFeatureCompilationTest.CompilationExclusionCliEnhancer; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.CommandLineEnhancer; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * Integration test that compiles all test mappers in the processor-module, excluding all classes that contain one of - * the following in their path/file name: - *
        - *
      • {@code /erronerous/}
      • - *
      • {@code *Erroneous*}
      • - *
      • {@code *Test.java}
      • - *
      • {@code /testutil/}
      • - *
      • possibly more, depending on the processor type - see {@link CompilationExclusionCliEnhancer}
      • - *
      - * - * @author Andreas Gudian - */ -@RunWith(ProcessorSuiteRunner.class) -@ProcessorSuite( - baseDir = "fullFeatureTest", - commandLineEnhancer = CompilationExclusionCliEnhancer.class, - processorTypes = { - ProcessorType.ORACLE_JAVA_8, - ProcessorType.ORACLE_JAVA_9, - ProcessorType.ECLIPSE_JDT_JAVA_8 -}) -public class FullFeatureCompilationTest { - /** - * Adds explicit exclusions of test mappers that are known or expected to not work with specific compilers. - * - * @author Andreas Gudian - */ - public static final class CompilationExclusionCliEnhancer implements CommandLineEnhancer { - @Override - public Collection getAdditionalCommandLineArguments(ProcessorType processorType) { - List additionalExcludes = new ArrayList<>(); - - // SPI not working correctly here.. (not picked up) - additionalExcludes.add( "org/mapstruct/ap/test/bugs/_1596/*.java" ); - - switch ( processorType ) { - case ORACLE_JAVA_9: - // TODO find out why this fails: - additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); - break; - default: - } - - Collection result = new ArrayList( additionalExcludes.size() ); - for ( int i = 0; i < additionalExcludes.size(); i++ ) { - result.add( "-DadditionalExclude" + i + "=" + additionalExcludes.get( i ) ); - } - - return result; - } - } -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/GradleIncrementalCompilationTest.java new file mode 100644 index 0000000000..3d496906c0 --- /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 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.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.MethodSource; +import org.junit.runners.Parameterized.Parameters; + +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; +import static org.gradle.testkit.runner.TaskOutcome.UP_TO_DATE; +import static org.hamcrest.core.StringContains.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +/** + *

      This is supposed to be run from the mapstruct root project folder. + * Otherwise, use -Dmapstruct_root=path_to_project. + */ +@DisabledForJreRange(min = JRE.JAVA_11) +public class GradleIncrementalCompilationTest { + private static Path rootPath; + private static String projectDir = "integrationtest/src/test/resources/gradleIncrementalCompilationTest"; + private static String compileTaskName = "compileJava"; + + @TempDir + File testBuildDir; + @TempDir + File testProjectDir; + + private GradleRunner runner; + private File sourceDirectory; + private List compileArgs; // Gradle compile task arguments + + @Parameters(name = "Gradle {0}") + public static List gradleVersions() { + return Arrays.asList( "5.0", "6.0" ); + } + + 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( ":" + compileTaskName ).getOutcome() ); + } + + private void assertRecompiled(BuildResult result, int recompiledCount) { + assertCompileOutcome( result, recompiledCount > 0 ? SUCCESS : UP_TO_DATE ); + assertThat( + result.getOutput(), + containsString( String.format( "Incremental compilation of %d classes completed", recompiledCount ) ) + ); + } + + 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( compileTaskName, buildDirPropertyArg, jarDirectoryArg ); + } + + @BeforeAll + public static void setupClass() throws Exception { + 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( projectDir ); + FileUtils.copyDirectory( gradleProjectPath.toFile(), testProjectDir ); + compileArgs = buildCompileArgs(); + sourceDirectory = new File( testProjectDir, "src/main/java" ); + runner = GradleRunner.create().withGradleVersion( gradleVersion ).withProjectDir( testProjectDir ); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + public 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") + public void testUpToDate(String gradleVersion) throws IOException { + setup( gradleVersion ); + getRunner().build(); + BuildResult secondBuildResult = getRunner().build(); + assertCompileOutcome( secondBuildResult, UP_TO_DATE ); + } + + @ParameterizedTest + @MethodSource("gradleVersions") + public 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") + public 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") + public 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/ImmutablesBuilderTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/ImmutablesBuilderTest.java deleted file mode 100644 index b6f6970d38..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/ImmutablesBuilderTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Filip Hrisafov - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "immutablesBuilderTest", - processorTypes = ProcessorSuite.ProcessorType.ALL_WITHOUT_PROCESSOR_PLUGIN) -public class ImmutablesBuilderTest { -} 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 ce0b191f65..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/Java8Test.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 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.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 840f5d9337..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/JaxbTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 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.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 65cd76f0d3..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/Jsr330Test.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 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.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/LombokBuilderTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/LombokBuilderTest.java deleted file mode 100644 index 7199cb1f34..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/LombokBuilderTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Eric Martineau - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "lombokBuilderTest", - processorTypes = { - ProcessorSuite.ProcessorType.ORACLE_JAVA_8, - ProcessorSuite.ProcessorType.ORACLE_JAVA_9, - } -) -public class LombokBuilderTest { -} 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..60784d206a --- /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) + void lombokBuilderTest() { + } + + @ProcessorTest(baseDir = "lombokModuleTest", processorTypes = { + ProcessorTest.ProcessorType.JAVAC, + ProcessorTest.ProcessorType.JAVAC_WITH_PATHS + }) + @EnabledForJreRange(min = JRE.JAVA_11) + @DisabledOnJre(versions = 27) + 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 55d06a3f14..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/NamingStrategyTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 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.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/ProtobufBuilderTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/ProtobufBuilderTest.java deleted file mode 100644 index cf648ca3c8..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/ProtobufBuilderTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * ECLIPSE_JDT_JAVA_8 is not working with Protobuf. Use all other available processor types. - * - * @author Christian Bandowski - */ -@RunWith(ProcessorSuiteRunner.class) -@ProcessorSuite(baseDir = "protobufBuilderTest", - processorTypes = { - ProcessorSuite.ProcessorType.ORACLE_JAVA_8, - ProcessorSuite.ProcessorType.ORACLE_JAVA_9, - }) -public class ProtobufBuilderTest { -} 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 bbcd7a529d..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/SimpleTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 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.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 1eca9383a2..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/SpringTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * @author Andreas Gudian - * - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite( baseDir = "springTest", processorTypes = ProcessorType.ALL ) -public class SpringTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/SuperTypeGenerationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/SuperTypeGenerationTest.java deleted file mode 100644 index b39f8e93d9..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/SuperTypeGenerationTest.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * Tests usage of MapStruct with another processor that generates supertypes of mapping source/target types. - * - * @author Gunnar Morling - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite(baseDir = "superTypeGenerationTest", processorTypes = ProcessorType.ORACLE_JAVA_8) -public class SuperTypeGenerationTest { -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/TargetTypeGenerationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/TargetTypeGenerationTest.java deleted file mode 100644 index 2cbd78f0f2..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/TargetTypeGenerationTest.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 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.runner.RunWith; -import org.mapstruct.itest.testutil.runner.ProcessorSuite; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner; - -/** - * Tests usage of MapStruct with another processor that generates the target type of a mapping method. - * - * @author Gunnar Morling - */ -@RunWith( ProcessorSuiteRunner.class ) -@ProcessorSuite(baseDir = "targetTypeGenerationTest", processorTypes = ProcessorType.ORACLE_JAVA_8) -public class TargetTypeGenerationTest { -} 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 916d513954..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuite.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 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.runner; - -import org.apache.maven.it.Verifier; - -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; - -/** - * Declares the content of the integration test. - *

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

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

      - * Types that require toolchains, will - * need the toolchains.xml file to be either installed in ~/m2, or alternatively passed to the mvn process using - * {@code mvn -DprocessorIntegrationTest.toolchainsFile=/path/to/toolchains.xml ...} - * - * @author Andreas Gudian - */ - enum ProcessorType { - - /** - * Use the same JDK that runs the mvn build to perform the processing - */ - ORACLE_JAVA_8( null, "javac", "1.8" ), - - /** - * Use an Oracle JDK 1.9 (or 1.9.x) via toolchain support to perform the processing - */ - ORACLE_JAVA_9( new Toolchain( "oracle", "9", "10" ), "javac", "1.9" ), - - /** - * Use the eclipse compiler with 1.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 processing variants, but without the maven-procesor-plugin - */ - ALL_WITHOUT_PROCESSOR_PLUGIN( ORACLE_JAVA_8, ORACLE_JAVA_9, ECLIPSE_JDT_JAVA_8), - - /** - * Use all available processing variants - */ - ALL( ORACLE_JAVA_8, ORACLE_JAVA_9, ECLIPSE_JDT_JAVA_8, PROCESSOR_PLUGIN_JAVA_8 ), - - /** - * Use all JDK8 compatible processing variants - */ - ALL_JAVA_8( ORACLE_JAVA_8, ECLIPSE_JDT_JAVA_8, PROCESSOR_PLUGIN_JAVA_8 ); - - private ProcessorType[] included = { }; - - private Toolchain toolchain; - private String compilerId; - private String sourceTargetVersion; - - ProcessorType(Toolchain toolchain, String compilerId, String sourceTargetVersion) { - this.toolchain = toolchain; - this.compilerId = compilerId; - this.sourceTargetVersion = sourceTargetVersion; - } - - ProcessorType(ProcessorType... included) { - this.included = included; - } - - /** - * @return the processor types that are grouped by this type - */ - public ProcessorType[] getIncluded() { - return included; - } - - /** - * @return the toolchain - */ - public Toolchain getToolchain() { - return toolchain; - } - - /** - * @return the compilerId - */ - public String getCompilerId() { - return compilerId; - } - - /** - * @return the sourceTargetVersion - */ - public String getSourceTargetVersion() { - return sourceTargetVersion; - } - } - - /** - * Can be configured to provide additional command line arguments for the invoked Maven process, depending on the - * {@link ProcessorType} the test is executed for. - * - * @author Andreas Gudian - */ - interface CommandLineEnhancer { - /** - * @param processorType the processor type for which the test is executed. - * @return additional command line arguments to be passed to the Maven {@link Verifier}. - */ - Collection getAdditionalCommandLineArguments(ProcessorType processorType); - } - - /** - * @return a path in the classpath that contains the maven module to run as integration test: {@code mvn clean test} - */ - String baseDir(); - - /** - * @return the variants to execute the integration tests with. See {@link ProcessorType}. - */ - ProcessorType[] processorTypes() default { ProcessorType.ALL }; - - /** - * @return the {@link CommandLineEnhancer} implementation. Must have a default constructor. - */ - Class commandLineEnhancer() default CommandLineEnhancer.class; -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuiteRunner.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuiteRunner.java deleted file mode 100644 index c2d20e0568..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/ProcessorSuiteRunner.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright 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.runner; - -import static org.apache.maven.it.util.ResourceExtractor.extractResourceToDestination; -import static org.apache.maven.shared.utils.io.FileUtils.copyURLToFile; -import static org.apache.maven.shared.utils.io.FileUtils.deleteDirectory; - -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Unmarshaller; - -import org.apache.maven.it.Verifier; -import org.junit.internal.AssumptionViolatedException; -import org.junit.internal.runners.model.EachTestNotifier; -import org.junit.runner.Description; -import org.junit.runner.notification.RunNotifier; -import org.junit.runner.notification.StoppedByUserException; -import org.junit.runners.ParentRunner; -import org.junit.runners.model.InitializationError; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.CommandLineEnhancer; -import org.mapstruct.itest.testutil.runner.ProcessorSuite.ProcessorType; -import org.mapstruct.itest.testutil.runner.ProcessorSuiteRunner.ProcessorTestCase; -import org.mapstruct.itest.testutil.runner.xml.Toolchains; -import org.mapstruct.itest.testutil.runner.xml.Toolchains.ProviderDescription; - -/** - * Runner for processor integration tests. Requires the annotation {@link ProcessorSuite} on the test class. - * - * @author Andreas Gudian - */ -public class ProcessorSuiteRunner extends ParentRunner { - - /** - * System property for specifying the location of the toolchains.xml file - */ - public static final String SYS_PROP_TOOLCHAINS_FILE = "processorIntegrationTest.toolchainsFile"; - - /** - * System property to enable remote debugging of the processor execution in the integration test - */ - public static final String SYS_PROP_DEBUG = "processorIntegrationTest.debug"; - - private static final File TOOLCHAINS_FILE = getToolchainsFile(); - - private static final Collection ENABLED_PROCESSOR_TYPES = detectSupportedTypes( TOOLCHAINS_FILE ); - - public static final class ProcessorTestCase { - private final String baseDir; - private final ProcessorType processor; - private final boolean ignored; - private final Constructor cliEnhancerConstructor; - - public ProcessorTestCase(String baseDir, ProcessorType processor, - Constructor cliEnhancerConstructor) { - this.baseDir = baseDir; - this.processor = processor; - this.cliEnhancerConstructor = cliEnhancerConstructor; - this.ignored = !ENABLED_PROCESSOR_TYPES.contains( processor ); - } - } - - private final List methods; - - /** - * @param clazz the test class - * @throws InitializationError in case the initialization fails - */ - public ProcessorSuiteRunner(Class clazz) throws InitializationError { - super( clazz ); - - ProcessorSuite suite = clazz.getAnnotation( ProcessorSuite.class ); - - if ( null == suite ) { - throw new InitializationError( "The test class must be annotated with " + ProcessorSuite.class.getName() ); - } - - if ( suite.processorTypes().length == 0 ) { - throw new InitializationError( "ProcessorSuite#processorTypes must not be empty" ); - } - - Constructor cliEnhancerConstructor = null; - if ( suite.commandLineEnhancer() != CommandLineEnhancer.class ) { - try { - cliEnhancerConstructor = suite.commandLineEnhancer().getConstructor(); - } - catch ( NoSuchMethodException e ) { - throw new InitializationError( - suite.commandLineEnhancer().getName() + " does not have a default constructor." ); - } - catch ( SecurityException e ) { - throw new InitializationError( e ); - } - } - - methods = initializeTestCases( suite, cliEnhancerConstructor ); - } - - private List initializeTestCases(ProcessorSuite suite, - Constructor cliEnhancerConstructor) { - List types = new ArrayList(); - - for ( ProcessorType compiler : suite.processorTypes() ) { - if ( compiler.getIncluded().length > 0 ) { - types.addAll( Arrays.asList( compiler.getIncluded() ) ); - } - else { - types.add( compiler ); - } - } - - List result = new ArrayList( types.size() ); - - for ( ProcessorType type : types ) { - result.add( new ProcessorTestCase( suite.baseDir(), type, cliEnhancerConstructor ) ); - } - - return result; - } - - @Override - protected List getChildren() { - return methods; - } - - @Override - protected Description describeChild(ProcessorTestCase child) { - return Description.createTestDescription( getTestClass().getJavaClass(), child.processor.name().toLowerCase() ); - } - - @Override - protected void runChild(ProcessorTestCase child, RunNotifier notifier) { - Description description = describeChild( child ); - EachTestNotifier testNotifier = new EachTestNotifier( notifier, description ); - - if ( child.ignored ) { - testNotifier.fireTestIgnored(); - } - else { - try { - testNotifier.fireTestStarted(); - doExecute( child, description ); - } - catch ( AssumptionViolatedException e ) { - testNotifier.fireTestIgnored(); - } - catch ( StoppedByUserException e ) { - throw e; - } - catch ( Throwable e ) { - testNotifier.addFailure( e ); - } - finally { - testNotifier.fireTestFinished(); - } - } - } - - private void doExecute(ProcessorTestCase child, Description description) throws Exception { - File destination = extractTest( child, description ); - PrintStream originalOut = System.out; - - final Verifier verifier; - if ( Boolean.getBoolean( SYS_PROP_DEBUG ) ) { - if ( child.processor.getToolchain() == null ) { - // when not using toolchains for a test, then the compiler is executed within the Maven JVM. So make - // sure we fork a new JVM for that, and let that new JVM use the command 'mvnDebug' instead of 'mvn' - verifier = new Verifier( destination.getCanonicalPath(), null, true, true ); - verifier.setDebugJvm( true ); - } - else { - verifier = new Verifier( destination.getCanonicalPath() ); - verifier.addCliOption( "-Pdebug-forked-javac" ); - } - } - else { - verifier = new Verifier( destination.getCanonicalPath() ); - } - - List goals = new ArrayList( 3 ); - - goals.add( "clean" ); - - try { - configureToolchains( child, verifier, goals, originalOut ); - configureProcessor( child, verifier ); - - verifier.addCliOption( "-Dcompiler-source-target-version=" + child.processor.getSourceTargetVersion() ); - - verifier.addCliOption( "-Dmapstruct-artifact-id=mapstruct" ); - - if ( Boolean.getBoolean( SYS_PROP_DEBUG ) ) { - originalOut.print( "Processor Integration Test: " ); - originalOut.println( "Listening for transport dt_socket at address: 8000 (in some seconds)" ); - } - - goals.add( "test" ); - - addAdditionalCliArguments( child, verifier ); - - originalOut.println( "executing " + child.processor.name().toLowerCase() ); - - verifier.executeGoals( goals ); - verifier.verifyErrorFreeLog(); - } - finally { - verifier.resetStreams(); - } - } - - private void addAdditionalCliArguments(ProcessorTestCase child, final Verifier verifier) throws Exception { - if ( child.cliEnhancerConstructor != null ) { - CommandLineEnhancer enhancer = child.cliEnhancerConstructor.newInstance(); - Collection additionalArgs = enhancer.getAdditionalCommandLineArguments( child.processor ); - - if ( additionalArgs != null ) { - for ( String arg : additionalArgs ) { - verifier.addCliOption( arg ); - } - } - } - } - - private void configureProcessor(ProcessorTestCase child, Verifier verifier) { - if ( child.processor.getCompilerId() != null ) { - verifier.addCliOption( "-Pgenerate-via-compiler-plugin" ); - verifier.addCliOption( "-Dcompiler-id=" + child.processor.getCompilerId() ); - } - else { - verifier.addCliOption( "-Pgenerate-via-processor-plugin" ); - } - } - - private void configureToolchains(ProcessorTestCase child, Verifier verifier, List goals, - PrintStream originalOut) { - if ( child.processor.getToolchain() != null ) { - verifier.addCliOption( "--toolchains" ); - verifier.addCliOption( TOOLCHAINS_FILE.getPath().replace( '\\', '/' ) ); - - Toolchain toolchain = child.processor.getToolchain(); - - verifier.addCliOption( "-Dtoolchain-jdk-vendor=" + toolchain.getVendor() ); - verifier.addCliOption( "-Dtoolchain-jdk-version=" + toolchain.getVersionRangeString() ); - - goals.add( "toolchains:toolchain" ); - } - } - - private File extractTest(ProcessorTestCase child, Description description) throws IOException { - File tempDirBase = new File( "target/tmp", description.getClassName() ).getCanonicalFile(); - - if ( !tempDirBase.exists() ) { - tempDirBase.mkdirs(); - } - - File parentPom = new File( tempDirBase, "pom.xml" ); - copyURLToFile( getClass().getResource( "/pom.xml" ), parentPom ); - - File tempDir = new File( tempDirBase, description.getMethodName() ); - deleteDirectory( tempDir ); - - return extractResourceToDestination( getClass(), "/" + child.baseDir, tempDir, true ); - } - - private static File getToolchainsFile() { - String specifiedToolchainsFile = System.getProperty( SYS_PROP_TOOLCHAINS_FILE ); - if ( null != specifiedToolchainsFile ) { - try { - File canonical = new File( specifiedToolchainsFile ).getCanonicalFile(); - if ( canonical.exists() ) { - return canonical; - } - - // check the path relative to the parent directory (allows specifying a path relative to the top-level - // aggregator module) - canonical = new File( "..", specifiedToolchainsFile ).getCanonicalFile(); - if ( canonical.exists() ) { - return canonical; - } - } - catch ( IOException e ) { - return null; - } - } - - // use default location - String defaultPath = System.getProperty( "user.home" ) + System.getProperty( "file.separator" ) + ".m2"; - return new File( defaultPath, "toolchains.xml" ); - } - - private static Collection detectSupportedTypes(File toolchainsFile) { - Toolchains toolchains; - try { - if ( toolchainsFile.exists() ) { - Unmarshaller unmarshaller = JAXBContext.newInstance( Toolchains.class ).createUnmarshaller(); - toolchains = (Toolchains) unmarshaller.unmarshal( toolchainsFile ); - } - else { - toolchains = null; - } - } - catch ( JAXBException e ) { - toolchains = null; - } - - Collection supported = new HashSet<>(); - for ( ProcessorType type : ProcessorType.values() ) { - if ( isSupported( type, toolchains ) ) { - supported.add( type ); - } - } - - return supported; - } - - private static boolean isSupported(ProcessorType type, Toolchains toolchains) { - if ( type.getToolchain() == null ) { - return true; - } - - if ( toolchains == null ) { - return false; - } - - Toolchain required = type.getToolchain(); - - for ( Toolchains.Toolchain tc : toolchains.getToolchains() ) { - if ( "jdk".equals( tc.getType() ) ) { - ProviderDescription desc = tc.getProviderDescription(); - if ( desc != null - && required.getVendor().equals( desc.getVendor() ) - && desc.getVersion() != null - && desc.getVersion().startsWith( required.getVersionMinInclusive() ) ) { - return true; - } - } - } - - return false; - } -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/Toolchain.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/Toolchain.java deleted file mode 100644 index 83b7f7f666..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/Toolchain.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 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.runner; - -/** - * Describes a required entry in the Maven toolchains.xml file. - * - * @author Andreas Gudian - */ -class Toolchain { - private final String vendor; - private final String versionMinInclusive; - private final String versionMaxExclusive; - - Toolchain(String vendor, String versionMinInclusive, String versionMaxExclusive) { - this.vendor = vendor; - this.versionMinInclusive = versionMinInclusive; - this.versionMaxExclusive = versionMaxExclusive; - } - - String getVendor() { - return vendor; - } - - String getVersionMinInclusive() { - return versionMinInclusive; - } - - /** - * @return the version range string to be used in the Maven execution to select the toolchain - */ - String getVersionRangeString() { - return "[" + versionMinInclusive + "," + versionMaxExclusive + ")"; - } -} diff --git a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/xml/Toolchains.java b/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/xml/Toolchains.java deleted file mode 100644 index 4f9b0c7e60..0000000000 --- a/integrationtest/src/test/java/org/mapstruct/itest/testutil/runner/xml/Toolchains.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 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.runner.xml; - -import java.util.ArrayList; -import java.util.List; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -/** - * JAXB representation of some of the parts in the Maven toolchains.xml file. - * - * @author Andreas Gudian - */ -@XmlRootElement -public class Toolchains { - @XmlElement(name = "toolchain") - private List toolchains = new ArrayList<>(); - - public List getToolchains() { - return toolchains; - } - - @Override - public String toString() { - return "Toolchains [toolchains=" + toolchains + "]"; - } - - public static class Toolchain { - @XmlElement - private String type; - - @XmlElement(name = "provides") - private ProviderDescription providerDescription; - - public String getType() { - return type; - } - - public ProviderDescription getProviderDescription() { - return providerDescription; - } - - @Override - public String toString() { - return "Toolchain [type=" + type + ", providerDescription=" + providerDescription + "]"; - } - } - - public static class ProviderDescription { - @XmlElement - private String version; - - @XmlElement - private String vendor; - - public String getVersion() { - return version; - } - - public String getVendor() { - return vendor; - } - - @Override - public String toString() { - return "ProviderDescription [version=" + version + ", vendor=" + vendor + "]"; - } - } -} diff --git a/integrationtest/src/test/resources/cdiTest/pom.xml b/integrationtest/src/test/resources/cdiTest/pom.xml index 30d35a421e..0b3c394e82 100644 --- a/integrationtest/src/test/resources/cdiTest/pom.xml +++ b/integrationtest/src/test/resources/cdiTest/pom.xml @@ -56,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 1787fd3c90..18a57ce25e 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 @@ -6,10 +6,11 @@ package org.mapstruct.itest.cdi; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.DecoratedWith; 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/SourceTargetMapper.java b/integrationtest/src/test/resources/cdiTest/src/main/java/org/mapstruct/itest/cdi/SourceTargetMapper.java index 3a46a20889..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 @@ -6,9 +6,10 @@ 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/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java b/integrationtest/src/test/resources/cdiTest/src/test/java/org/mapstruct/itest/cdi/CdiBasedMapperTest.java index 702ab6d255..0c343fce27 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 @@ -38,7 +38,9 @@ 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" ); } 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..ddc834cc8e --- /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.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ + +public class DefaultPackageTest { + + @Test + public 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..42ed70b18e --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class TextBlocksTest { + + @Test + public 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/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml b/integrationtest/src/test/resources/faultyAstModifyingAnnotationProcessorTest/generator/pom.xml new file mode 100644 index 0000000000..08390085dd --- /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 + + + junit + junit + 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..90c95aa83b --- /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 + + + + junit + junit + 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..06bfa3c533 --- /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.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 + */ +public class FaultyAstModifyingTestTest { + + @Test + public 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/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 9a7b85426f..670d5cff92 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -25,6 +25,13 @@ x x x + x + x + x + x + x + x + x @@ -40,11 +47,21 @@ **/*Test.java **/testutil/**/*.java **/spi/**/*.java + **/kotlin/**/*.java ${additionalExclude0} ${additionalExclude1} ${additionalExclude2} ${additionalExclude3} ${additionalExclude4} + ${additionalExclude5} + ${additionalExclude6} + ${additionalExclude7} + ${additionalExclude8} + ${additionalExclude9} + ${additionalExclude10} + ${additionalExclude11} + ${additionalExclude12} + ${additionalExclude13} @@ -60,6 +77,14 @@ javax.inject javax.inject + + jakarta.inject + jakarta.inject-api + + + jakarta.enterprise + jakarta.enterprise.cdi-api + @@ -75,5 +100,35 @@ joda-time joda-time + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + + + + + jdk-11-or-newer + + [11 + + + + javax.xml.bind + jaxb-api + provided + true + + + org.glassfish.jaxb + jaxb-runtime + provided + true + + + + diff --git a/integrationtest/src/test/resources/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..6137ff0eca --- /dev/null +++ b/integrationtest/src/test/resources/gradleIncrementalCompilationTest/src/main/java/org/mapstruct/itest/gradle/lib/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.itest.gradle.lib; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; + +import org.mapstruct.itest.gradle.model.Target; +import org.mapstruct.itest.gradle.model.Source; + +@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/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..3b76aad437 --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/main/java/org/mapstruct/itest/jakarta/jaxb/SourceTargetMapper.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; + +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..b81c946d9c --- /dev/null +++ b/integrationtest/src/test/resources/jakartaJaxbTest/src/test/java/org/mapstruct/itest/jakarta/jaxb/JakartaJaxbBasedMapperTest.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.itest.jakarta.jaxb; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.ByteArrayOutputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Marshaller; + +import org.junit.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; + +/** + * Test for generation of Jakarta JAXB based mapper implementations. + * + * @author Iaroslav Bogdanchikov + */ +public class JakartaJaxbBasedMapperTest { + + @Test + public 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 + public void underscores() throws ParseException, JAXBException { + + SourceTargetMapper mapper = SourceTargetMapper.INSTANCE; + + SubTypeDto source1 = new SubTypeDto(); + source1.setInheritedCamelCase("InheritedCamelCase"); + source1.setInheritedUnderscore("InheritedUnderscore"); + source1.setDeclaredCamelCase("DeclaredCamelCase"); + source1.setDeclaredUnderscore("DeclaredUnderscore"); + + SubType target = mapper.dtoToSubType( source1 ); + + SubTypeDto source2 = mapper.subTypeToDto( target ); + + assertThat( source2.getInheritedCamelCase() ).isEqualTo( source1.getInheritedCamelCase() ); + assertThat( source2.getInheritedUnderscore() ).isEqualTo( source1.getInheritedUnderscore() ); + assertThat( source2.getDeclaredCamelCase() ).isEqualTo( source1.getDeclaredCamelCase() ); + assertThat( source2.getDeclaredUnderscore() ).isEqualTo( source1.getDeclaredUnderscore() ); + } + + private Date createDate(String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); + return sdf.parse( date ); + } + + private String toXml(JAXBElement element) throws JAXBException { + JAXBContext jc = JAXBContext.newInstance( element.getValue().getClass() ); + Marshaller marshaller = jc.createMarshaller(); + marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE ); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + marshaller.marshal( element, baos ); + return baos.toString(); + } +} diff --git a/integrationtest/src/test/resources/java8Test/src/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 407a4c3b52..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 @@ -10,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 e0e2f791a7..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 @@ -15,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/itest/java8/Java8Mapper.java b/integrationtest/src/test/resources/java8Test/src/main/java/org/mapstruct/itest/java8/Java8Mapper.java index 4acd23d08e..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 @@ -14,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/jaxbTest/pom.xml b/integrationtest/src/test/resources/jaxbTest/pom.xml index 49f6c28cef..0e69e23e01 100644 --- a/integrationtest/src/test/resources/jaxbTest/pom.xml +++ b/integrationtest/src/test/resources/jaxbTest/pom.xml @@ -47,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 445f5b37ce..0000000000 --- a/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/JaxbMapper.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 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; - -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/SourceTargetMapper.java b/integrationtest/src/test/resources/jaxbTest/src/main/java/org/mapstruct/itest/jaxb/SourceTargetMapper.java index 4fb4434a46..c0a56f8169 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 @@ -20,8 +20,7 @@ @Mapper(uses = { org.mapstruct.itest.jaxb.xsd.test1.ObjectFactory.class, org.mapstruct.itest.jaxb.xsd.test2.ObjectFactory.class, - org.mapstruct.itest.jaxb.xsd.underscores.ObjectFactory.class, - JaxbMapper.class + org.mapstruct.itest.jaxb.xsd.underscores.ObjectFactory.class }) public interface SourceTargetMapper { diff --git a/integrationtest/src/test/resources/jsr330Test/pom.xml b/integrationtest/src/test/resources/jsr330Test/pom.xml index c0372831f1..9a7dbcbc8a 100644 --- a/integrationtest/src/test/resources/jsr330Test/pom.xml +++ b/integrationtest/src/test/resources/jsr330Test/pom.xml @@ -34,8 +34,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 137f234c26..77f5ec558a 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 @@ -6,10 +6,11 @@ package org.mapstruct.itest.jsr330; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.DecoratedWith; 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 cdec6dc19d..f088e21de4 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 @@ -6,10 +6,11 @@ package org.mapstruct.itest.jsr330; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.DecoratedWith; 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 d41db89dd5..0fb513c99b 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SecondSourceTargetMapperDecorator.java @@ -5,8 +5,8 @@ */ package org.mapstruct.itest.jsr330; -import javax.inject.Inject; -import javax.inject.Named; +import jakarta.inject.Inject; +import jakarta.inject.Named; public abstract class SecondSourceTargetMapperDecorator implements SecondDecoratedSourceTargetMapper { diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapper.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapper.java index c5ba3670fb..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 @@ -6,9 +6,10 @@ 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 e7f3423e4b..7f4b968227 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/SourceTargetMapperDecorator.java @@ -5,8 +5,8 @@ */ package org.mapstruct.itest.jsr330; -import javax.inject.Inject; -import javax.inject.Named; +import jakarta.inject.Inject; +import jakarta.inject.Named; public abstract class SourceTargetMapperDecorator implements DecoratedSourceTargetMapper { diff --git a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/other/DateMapper.java b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/other/DateMapper.java index 817ab2aa53..d97b168776 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/other/DateMapper.java +++ b/integrationtest/src/test/resources/jsr330Test/src/main/java/org/mapstruct/itest/jsr330/other/DateMapper.java @@ -9,8 +9,8 @@ import java.text.SimpleDateFormat; import java.util.Date; -import javax.inject.Named; -import javax.inject.Singleton; +import jakarta.inject.Named; +import jakarta.inject.Singleton; @Singleton @Named diff --git a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java index 26a9863576..525930f0ec 100644 --- a/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java +++ b/integrationtest/src/test/resources/jsr330Test/src/test/java/org/mapstruct/itest/jsr330/Jsr330BasedMapperTest.java @@ -5,8 +5,8 @@ */ package org.mapstruct.itest.jsr330; -import javax.inject.Inject; -import javax.inject.Named; +import jakarta.inject.Inject; +import jakarta.inject.Named; import org.junit.Test; import org.junit.runner.RunWith; 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..513f080a8b --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.mapstruct.itest.kotlin.data.CustomerDto; +import org.mapstruct.itest.kotlin.data.CustomerEntity; +import org.mapstruct.itest.kotlin.data.CustomerMapper; + +public class KotlinDataTest { + + @Test + public 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 + public 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 index 92ece809fe..5d43efb1d3 100644 --- a/integrationtest/src/test/resources/lombokBuilderTest/pom.xml +++ b/integrationtest/src/test/resources/lombokBuilderTest/pom.xml @@ -25,5 +25,11 @@ 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/Person.java b/integrationtest/src/test/resources/lombokBuilderTest/src/main/java/org/mapstruct/itest/lombok/Person.java index faefc5aa1b..fcfaa6f49f 100644 --- 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 @@ -8,9 +8,7 @@ import lombok.Builder; import lombok.Getter; -//TODO make MapStruct DefaultBuilderProvider work with custom builder name -//@Builder(builderMethodName = "foo", buildMethodName = "create", builderClassName = "Builder") -@Builder(builderClassName = "Builder") +@Builder(builderMethodName = "foo", buildMethodName = "create", builderClassName = "Builder") @Getter public class Person { private final String name; diff --git a/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java index 4a58bb72be..6053f294c3 100644 --- a/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java +++ b/integrationtest/src/test/resources/lombokBuilderTest/src/test/java/org/mapstruct/itest/lombok/LombokMapperTest.java @@ -19,13 +19,13 @@ public class LombokMapperTest { @Test public void testSimpleImmutableBuilderHappyPath() { - PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.builder() + PersonDto personDto = PersonMapper.INSTANCE.toDto( Person.foo() .age( 33 ) .name( "Bob" ) .address( Address.builder() .addressLine( "Wild Drive" ) .build() ) - .build() ); + .create() ); assertThat( personDto.getAge() ).isEqualTo( 33 ); assertThat( personDto.getName() ).isEqualTo( "Bob" ); assertThat( personDto.getAddress() ).isNotNull(); diff --git a/integrationtest/src/test/resources/lombokModuleTest/pom.xml b/integrationtest/src/test/resources/lombokModuleTest/pom.xml new file mode 100644 index 0000000000..16b32cfb4f --- /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.22 + + + 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..f8b702896d --- /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.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 + */ +public class LombokMapperTest { + + @Test + public 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 + public 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..805662c806 --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.mapstruct.itest.modules.CustomerDto; +import org.mapstruct.itest.modules.CustomerEntity; +import org.mapstruct.itest.modules.CustomerMapper; + +public class ModulesTest { + + @Test + public 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/pom.xml b/integrationtest/src/test/resources/pom.xml index d2ed9acdca..3053b211e1 100644 --- a/integrationtest/src/test/resources/pom.xml +++ b/integrationtest/src/test/resources/pom.xml @@ -24,13 +24,8 @@ ${mapstruct.version} - mapstruct - - - - 1.7.1 @@ -69,35 +64,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} @@ -137,7 +128,7 @@ ${project.groupId} - \${mapstruct-artifact-id} + mapstruct ${mapstruct.version} provided @@ -165,19 +156,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 index ecbbf1d6d8..c0d8e2a81b 100644 --- a/integrationtest/src/test/resources/protobufBuilderTest/pom.xml +++ b/integrationtest/src/test/resources/protobufBuilderTest/pom.xml @@ -23,7 +23,7 @@ 1.6.0 - 0.5.1 + 0.6.1 @@ -55,10 +55,10 @@ compile-custom - com.google.protobuf:protoc:3.2.0:exe:${os.detected.classifier} + com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier} grpc-java - io.grpc:protoc-gen-grpc-java:1.2.0:exe:${os.detected.classifier} + io.grpc:protoc-gen-grpc-java:1.47.0:exe:${os.detected.classifier} diff --git a/integrationtest/src/test/resources/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..a763359a98 --- /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.itest.records.module1.SourceRootRecord; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@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..5f7a99273a --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.mapstruct.itest.records.module1.SourceRootRecord; +import org.mapstruct.itest.records.module1.SourceNestedRecord; + +public class RecordsTest { + + @Test + public 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..2f274792b8 --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.mapstruct.itest.records.api.CustomerDto; +import org.mapstruct.itest.records.mapper.CustomerEntity; +import org.mapstruct.itest.records.mapper.CustomerMapper; + +public class RecordsTest { + + @Test + public 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 + public 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..2f77e8d49f --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.mapstruct.itest.records.CustomerDto; +import org.mapstruct.itest.records.CustomerEntity; +import org.mapstruct.itest.records.CustomerMapper; + +public class RecordsTest { + + @Test + public 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 + public 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 + public 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 + public 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 + public void shouldMapMemberRecord() { + MemberEntity member = MemberMapper.INSTANCE.fromRecord( new MemberDto( true, false ) ); + + assertThat( member ).isNotNull(); + assertThat( member.getIsActive() ).isTrue(); + assertThat( member.getPremium() ).isFalse(); + } + + @Test + public void shouldMapIntoMemberRecord() { + MemberEntity entity = new MemberEntity(); + entity.setIsActive( false ); + entity.setPremium( true ); + + MemberDto value = MemberMapper.INSTANCE.toRecord( entity ); + + assertThat( value ).isNotNull(); + assertThat( value.isActive() ).isEqualTo( false ); + assertThat( value.premium() ).isEqualTo( true ); + } + + @Test + public 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..c8ccaf1a65 --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class NestedRecordsTest { + + @Test + public 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..379341ff66 --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +public class SealedSubclassTest { + + @Test + public 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 + public 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 + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + CarDto carDto = new CarDto(); + carDto.setMaker( "BenZ" ); + vehiclesDto.getVehicles().add( carDto ); + + VehicleCollection result = SealedSubclassMapper.INSTANCE.mapInverse( vehiclesDto ); + + assertThat( result.getVehicles() ) + .extracting( Vehicle::getVehicleManufacturingCompany ) + .containsExactly( "BenZ" ); + } +} diff --git a/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetAbstractMapper.java b/integrationtest/src/test/resources/simpleTest/src/main/java/org/mapstruct/itest/simple/SourceTargetAbstractMapper.java index c5a222a6ca..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 @@ -16,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 3de0b84722..b3fd443c6b 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 @@ -17,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/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 f1bbf87d3a..a020ab2b27 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 @@ -6,10 +6,11 @@ package org.mapstruct.itest.spring; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.DecoratedWith; 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 88e2906300..7c04262595 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 @@ -6,10 +6,11 @@ package org.mapstruct.itest.spring; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.DecoratedWith; 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/SourceTargetMapper.java b/integrationtest/src/test/resources/springTest/src/main/java/org/mapstruct/itest/spring/SourceTargetMapper.java index 06e4be4d22..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 @@ -6,9 +6,10 @@ 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/superTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml index 1b84638ef2..5ab2d0d18f 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/generator/pom.xml @@ -32,7 +32,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -proc:none diff --git a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml index 07e89a9808..d1e1dd7dff 100644 --- a/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/superTypeGenerationTest/usage/pom.xml @@ -23,7 +23,6 @@ junit junit - 4.12 test @@ -39,7 +38,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -XprintProcessorInfo diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml index bf0d704851..67df383a18 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/generator/pom.xml @@ -32,7 +32,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -proc:none diff --git a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml index 8b0852ff07..bd06b79a49 100644 --- a/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml +++ b/integrationtest/src/test/resources/targetTypeGenerationTest/usage/pom.xml @@ -23,7 +23,6 @@ junit junit - 4.12 test @@ -39,7 +38,6 @@ org.apache.maven.plugins maven-compiler-plugin - 3.1 -XprintProcessorInfo 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..6ac3a01297 --- /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 + + + + junit + junit + 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..2c17279ff8 --- /dev/null +++ b/integrationtest/src/test/resources/usesTypeGenerationTest/generator/src/main/java/org/mapstruct/itest/usestypegeneration/UsesTypeGenerationProcessor.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.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..79696df47d --- /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 + + + + junit + junit + 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..e715e66620 --- /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.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 + */ +public class GeneratedUsesTypeTest { + + @Test + public 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/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 db190043a8..c4ba63948d 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -11,30 +11,48 @@ org.mapstruct mapstruct-parent - 1.4.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 - - 3.0.0-M1 - 3.0.0-M3 - 3.1.0 - 4.0.3.RELEASE - 0.26.0 - 8.18 - + + 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.12.0 1 - 3.11.1 + 3.27.7 - + jdt_apt + + 1.8 + 3.25.5 + 2.3.2 + 2.3.0 @@ -50,7 +68,12 @@ gunnarmorling Gunnar Morling gunnar@mapstruct.org - http://www.gunnarmorling.de/ + https://www.morling.dev/ + + + filiphr + Filip Hrisafov + https://github.com/filiphr/ @@ -65,12 +88,12 @@ sonatype-nexus-staging Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/ sonatype-nexus-snapshots Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots/ + https://central.sonatype.com/repository/maven-snapshots/ @@ -80,8 +103,8 @@ - Travis CI - https://travis-ci.org/mapstruct/mapstruct + Github Actions + https://github.com/mapstruct/mapstruct/actions @@ -96,7 +119,7 @@ org.freemarker freemarker - 2.3.21 + 2.3.34 org.assertj @@ -106,17 +129,22 @@ com.google.guava guava - 19.0 + 32.0.0-jre - com.jolira - hickory - ${com.jolira.hickory.version} + org.mapstruct.tools.gem + gem-api + ${org.mapstruct.gem.version} + + + org.mapstruct.tools.gem + gem-processor + ${org.mapstruct.gem.version} junit junit - 4.12 + 4.13.1 com.puppycrawl.tools @@ -124,33 +152,69 @@ ${com.puppycrawl.tools.checkstyle.version} + + org.jetbrains.kotlin + kotlin-bom + ${kotlin.version} + pom + import + + + org.jetbrains.kotlin + kotlin-metadata-jvm + ${kotlin.version} + + + org.junit + junit-bom + ${org.junit.jupiter.version} + pom + import + + + org.junit-pioneer + junit-pioneer + ${junit-pioneer.version} + + javax.enterprise cdi-api - 1.2 + 2.0.SP1 + + + jakarta.enterprise + jakarta.enterprise.cdi-api + 4.0.1 javax.inject javax.inject 1 + + jakarta.inject + jakarta.inject-api + 2.0.1 + org.jboss.arquillian arquillian-bom - 1.0.2.Final + 1.6.0.Final import pom org.jboss.arquillian.container arquillian-weld-se-embedded-1.1 - 1.0.0.CR7 + 1.0.0.Final org.jboss.weld - weld-core - 2.3.2.Final + weld-core-impl + 3.1.8.Final + test org.glassfish @@ -184,7 +248,7 @@ org.projectlombok lombok - 1.16.18 + 1.18.30 org.immutables @@ -199,7 +263,7 @@ com.google.protobuf protobuf-java - 3.6.0 + ${protobuf.version} org.inferred @@ -211,7 +275,31 @@ joda-time joda-time - 2.9 + 2.12.5 + + + + + + javax.xml.bind + jaxb-api + 2.3.1 + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + + + + jakarta.xml.bind + jakarta.xml.bind-api + 3.0.1 + + + com.sun.xml.bind + jaxb-impl + 3.0.2 @@ -223,12 +311,27 @@ org.codehaus.plexus plexus-container-default - 1.6 + 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.20 + 3.0.24 + + + commons-io + commons-io + 2.15.0 @@ -268,12 +371,12 @@ org.apache.maven.plugins maven-assembly-plugin - 3.1.1 + 3.8.0 org.apache.maven.plugins maven-checkstyle-plugin - 3.0.0 + 3.6.0 build-config/checkstyle.xml true @@ -287,7 +390,7 @@ specified as patterns within a source folder, so we can't exclude generated-sources altogether --> - **/*Prism.java,*/itest/jaxb/xsd/* + **/*Gem.java,*/itest/jaxb/xsd/* @@ -305,35 +408,26 @@ org.apache.maven.plugins maven-clean-plugin - 3.1.0 + 3.5.0 org.apache.maven.plugins maven-compiler-plugin - 3.8.0 - - 1.8 - 1.8 - + 3.15.0 org.apache.maven.plugins maven-dependency-plugin - 3.1.1 + 3.9.0 org.apache.maven.plugins maven-deploy-plugin - 3.0.0-M1 + 3.1.4 true - - org.apache.maven.plugins - maven-gpg-plugin - 1.6 - org.apache.maven.plugins maven-enforcer-plugin @@ -349,7 +443,7 @@ org.apache.felix maven-bundle-plugin - 4.0.0 + 5.1.1 bundle-manifest @@ -363,12 +457,12 @@ org.apache.maven.plugins maven-install-plugin - 3.0.0-M1 + 3.1.4 org.apache.maven.plugins maven-jar-plugin - 3.1.1 + 3.5.0 org.apache.maven.plugins @@ -376,39 +470,24 @@ ${org.apache.maven.plugins.javadoc.version} true - org.mapstruct.ap.internal.prism;org.mapstruct.itest.jaxb.xsd.* + org.mapstruct.ap.internal.gem;org.mapstruct.itest.jaxb.xsd.* 8 - - org.apache.maven.plugins - maven-release-plugin - 2.5.3 - - -DskipTests ${add.release.arguments} - clean install - false - true - @{project.version} - true - false - release - - org.apache.maven.plugins maven-resources-plugin - 3.1.0 + 3.4.0 org.apache.maven.plugins maven-source-plugin - 3.0.1 + 3.4.0 org.apache.maven.plugins maven-site-plugin - 3.7.1 + 3.21.0 org.apache.maven.plugins @@ -416,32 +495,41 @@ ${org.apache.maven.plugins.surefire.version} ${forkCount} - -Xms1024m -Xmx3072m org.apache.maven.plugins maven-shade-plugin - 3.2.0 + 3.6.1 - com.mycila.maven-license-plugin - maven-license-plugin - 1.10.b1 + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + com.mycila + license-maven-plugin + 4.6 org.codehaus.mojo animal-sniffer-maven-plugin - 1.17 + 1.20 org.ow2.asm asm - 6.2.1 + 7.0 + + org.codehaus.mojo + versions-maven-plugin + 2.16.2 + org.eclipse.m2e lifecycle-mapping @@ -496,7 +584,7 @@ org.jacoco jacoco-maven-plugin - 0.8.3 + 0.8.14 org.jvnet.jaxb2.maven2 @@ -521,7 +609,7 @@ com.github.siom79.japicmp japicmp-maven-plugin - 0.13.1 + 0.25.4 verify @@ -550,41 +638,54 @@ + + 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 - **/travis-settings.xml - **/eclipse-formatter-config.xml - **/forbidden-apis.txt - **/checkstyle-for-generated-sources.xml - **/nb-configuration.xml - maven-settings.xml - readme.md - CONTRIBUTING.md - .gitattributes - .gitignore - .factorypath - .checkstyle - *.yml - **/*.asciidoc - **/binding.xjb - + + +
      ${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 @@ -614,6 +715,9 @@ java18 1.0 + + org.mapstruct.ap.internal.util.IgnoreJRERequirement + @@ -633,7 +737,7 @@ - [1.8,) + [${minimum.java.version},) @@ -672,12 +776,49 @@ + + org.codehaus.mojo + flatten-maven-plugin + + + + flatten + package + + flatten + + + true + ossrh + + expand + + + + + flatten-clean + clean + + clean + + + + + + org.codehaus.mojo + versions-maven-plugin + - release + publication + + + release + + @@ -704,18 +845,88 @@ + + + + + stage + + local::file:${maven.multiModuleProjectDirectory}/target/staging-deploy + + + deploy + + + + jreleaser + + - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - + org.jreleaser + jreleaser-maven-plugin + ${jreleaser.plugin.version} + + true + + + Mapstruct + + https://mapstruct.org/ + https://mapstruct.org/documentation/stable/reference/html/ + + + + ALWAYS + 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 + + + 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 f9c649c168..c753e3c1e4 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ org.mapstruct mapstruct-parent - 1.4.0-SNAPSHOT + 1.7.0-SNAPSHOT parent/pom.xml @@ -27,7 +27,6 @@ core core-jdk8 processor - integrationtest true @@ -36,10 +35,14 @@ - com.mycila.maven-license-plugin - maven-license-plugin + com.mycila + license-maven-plugin -
      etc/license.txt
      + + +
      etc/license.txt
      +
      +
      XML_STYLE SLASHSTAR_STYLE @@ -71,5 +74,17 @@ distribution
      + + test + + + release + !true + + + + integrationtest + +
      diff --git a/processor/pom.xml b/processor/pom.xml index 4f792da14a..622ba924ee 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -12,7 +12,7 @@ org.mapstruct mapstruct-parent - 1.4.0-SNAPSHOT + 1.7.0-SNAPSHOT ../parent/pom.xml @@ -24,6 +24,7 @@ + 21 @@ -32,34 +33,39 @@ org.freemarker freemarker - - - - com.jolira - hickory - provided - true + org.mapstruct.tools.gem + gem-api ${project.groupId} mapstruct provided + + org.jetbrains.kotlin + kotlin-metadata-jvm + provided + + + org.jetbrains.kotlin + kotlin-compiler-embeddable + test + org.eclipse.tycho tycho-compiler-jdt - provided - true + test - junit - junit + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine test @@ -88,6 +94,16 @@ javax.inject test + + jakarta.inject + jakarta.inject-api + test + + + jakarta.enterprise + jakarta.enterprise.cdi-api + test + @@ -113,12 +129,31 @@ test + + org.junit.platform + junit-platform-launcher + test + + + + org.junit-pioneer + junit-pioneer + test + + joda-time joda-time test + + + jakarta.xml.bind + jakarta.xml.bind-api + provided + true + @@ -132,10 +167,18 @@ org.mapstruct.processor + annotation-processor
      + + org.apache.maven.plugins + maven-javadoc-plugin + + all,-missing + + org.apache.maven.plugins maven-surefire-plugin @@ -182,6 +225,12 @@ true + + + org.freemarker:* + org.mapstruct.tools.gem:gem-api + + org.freemarker:freemarker @@ -189,17 +238,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 @@ -223,14 +299,85 @@ + + org.jetbrains.kotlin + + kotlin-maven-plugin + + false + + + + kotlin-compile + compile + + compile + + + ${minimum.java.version} + + src/main/kotlin + + src/main/java + + + + + kotlin-test-compile + test-compile + + test-compile + + + ${minimum.java.version} + + src/test/java + + + + + org.apache.maven.plugins maven-compiler-plugin - - net.java.dev.hickory.prism.internal.PrismGenerator - + ${minimum.java.version} + + + org.mapstruct.tools.gem + gem-processor + ${org.mapstruct.gem.version} + + + + + + + default-compile + none + + + default-testCompile + none + + + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + + testCompile + + + org.apache.maven.plugins @@ -318,8 +465,8 @@ javax.xml.bind jaxb-api - 2.3.1 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 4878f3f989..32e65c7047 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -17,30 +17,34 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedOptions; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.Name; +import javax.lang.model.element.QualifiedNameable; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.ElementKindVisitor6; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementKindVisitor8; import javax.tools.Diagnostic.Kind; +import org.mapstruct.ap.internal.gem.MapperGem; import org.mapstruct.ap.internal.model.Mapper; +import org.mapstruct.ap.internal.option.MappingOption; import org.mapstruct.ap.internal.option.Options; -import org.mapstruct.ap.internal.prism.MapperPrism; -import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; import org.mapstruct.ap.internal.processor.DefaultModelElementProcessorContext; import org.mapstruct.ap.internal.processor.ModelElementProcessor; import org.mapstruct.ap.internal.processor.ModelElementProcessor.ProcessorContext; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.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; @@ -65,9 +69,9 @@ *
    9. if no error occurred, write out the model into Java source files
    10. * *

      - * 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 @@ -78,13 +82,6 @@ * @author Gunnar Morling */ @SupportedAnnotationTypes("org.mapstruct.Mapper") -@SupportedOptions({ - MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, - MappingProcessor.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT, - MappingProcessor.UNMAPPED_TARGET_POLICY, - MappingProcessor.DEFAULT_COMPONENT_MODEL, - MappingProcessor.VERBOSE -}) public class MappingProcessor extends AbstractProcessor { /** @@ -92,13 +89,35 @@ public class MappingProcessor extends AbstractProcessor { */ private static final boolean ANNOTATIONS_CLAIMED_EXCLUSIVELY = false; - protected static final String SUPPRESS_GENERATOR_TIMESTAMP = "mapstruct.suppressGeneratorTimestamp"; - protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = - "mapstruct.suppressGeneratorVersionInfoComment"; - protected static final String UNMAPPED_TARGET_POLICY = "mapstruct.unmappedTargetPolicy"; - protected static final String DEFAULT_COMPONENT_MODEL = "mapstruct.defaultComponentModel"; - protected static final String ALWAYS_GENERATE_SERVICE_FILE = "mapstruct.alwaysGenerateServicesFile"; - protected static final String VERBOSE = "mapstruct.verbose"; + // CHECKSTYLE:OFF + // Deprecated options, kept for backwards compatibility. + // They will be removed in a future release. + @Deprecated + protected static final String SUPPRESS_GENERATOR_TIMESTAMP = MappingOption.SUPPRESS_GENERATOR_TIMESTAMP.getOptionName(); + @Deprecated + protected static final String SUPPRESS_GENERATOR_VERSION_INFO_COMMENT = MappingOption.SUPPRESS_GENERATOR_VERSION_INFO_COMMENT.getOptionName(); + @Deprecated + protected static final String UNMAPPED_TARGET_POLICY = MappingOption.UNMAPPED_TARGET_POLICY.getOptionName(); + @Deprecated + protected static final String UNMAPPED_SOURCE_POLICY = MappingOption.UNMAPPED_SOURCE_POLICY.getOptionName(); + @Deprecated + protected static final String DEFAULT_COMPONENT_MODEL = MappingOption.DEFAULT_COMPONENT_MODEL.getOptionName(); + @Deprecated + protected static final String DEFAULT_INJECTION_STRATEGY = MappingOption.DEFAULT_INJECTION_STRATEGY.getOptionName(); + @Deprecated + protected static final String ALWAYS_GENERATE_SERVICE_FILE = MappingOption.ALWAYS_GENERATE_SERVICE_FILE.getOptionName(); + @Deprecated + protected static final String DISABLE_BUILDERS = MappingOption.DISABLE_BUILDERS.getOptionName(); + @Deprecated + protected static final String VERBOSE = MappingOption.VERBOSE.getOptionName(); + @Deprecated + protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = MappingOption.NULL_VALUE_ITERABLE_MAPPING_STRATEGY.getOptionName(); + @Deprecated + protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = MappingOption.NULL_VALUE_MAP_MAPPING_STRATEGY.getOptionName(); + // CHECKSTYLE:ON + + private final Set additionalSupportedOptions; + private final String additionalSupportedOptionsError; private Options options; @@ -111,34 +130,42 @@ public class MappingProcessor extends AbstractProcessor { *

      * If the hierarchy of a mapper's source/target types is never completed (i.e. the missing super-types are not * generated by other processors), this mapper will not be generated; That's fine, the compiler will raise an error - * due to the inconsistent Java types used as source or target anyways. + * due to the inconsistent Java types used as source or target anyway. */ - private Set deferredMappers = new HashSet<>(); + private Set deferredMappers = new HashSet<>(); + + public MappingProcessor() { + Set additionalSupportedOptions; + String additionalSupportedOptionsError; + try { + additionalSupportedOptions = resolveAdditionalSupportedOptions(); + additionalSupportedOptionsError = null; + } + catch ( IllegalStateException ex ) { + additionalSupportedOptions = Collections.emptySet(); + additionalSupportedOptionsError = ex.getMessage(); + } + this.additionalSupportedOptions = additionalSupportedOptions; + this.additionalSupportedOptionsError = additionalSupportedOptionsError; + } @Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init( processingEnv ); - options = createOptions(); + options = new Options( processingEnv.getOptions() ); annotationProcessorContext = new AnnotationProcessorContext( processingEnv.getElementUtils(), processingEnv.getTypeUtils(), processingEnv.getMessager(), - options.isVerbose() + options.isDisableBuilders(), + options.isVerbose(), + resolveAdditionalOptions( processingEnv.getOptions() ) ); - } - - private Options createOptions() { - String unmappedTargetPolicy = processingEnv.getOptions().get( UNMAPPED_TARGET_POLICY ); - return new Options( - Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), - Boolean.valueOf( processingEnv.getOptions().get( SUPPRESS_GENERATOR_VERSION_INFO_COMMENT ) ), - unmappedTargetPolicy != null ? ReportingPolicyPrism.valueOf( unmappedTargetPolicy.toUpperCase() ) : null, - processingEnv.getOptions().get( DEFAULT_COMPONENT_MODEL ), - Boolean.valueOf( processingEnv.getOptions().get( ALWAYS_GENERATE_SERVICE_FILE ) ), - Boolean.valueOf( processingEnv.getOptions().get( VERBOSE ) ) - ); + if ( additionalSupportedOptionsError != null ) { + processingEnv.getMessager().printMessage( Kind.ERROR, additionalSupportedOptionsError ); + } } @Override @@ -160,10 +187,54 @@ public boolean process(final Set annotations, final 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). @@ -171,7 +242,8 @@ public boolean process(final Set annotations, final Round private Set getAndResetDeferredMappers() { Set deferred = new HashSet<>( deferredMappers.size() ); - for (TypeElement element : deferredMappers ) { + for ( DeferredMapper deferredMapper : deferredMappers ) { + TypeElement element = deferredMapper.deferredMapperElement; deferred.add( processingEnv.getElementUtils().getTypeElement( element.getQualifiedName() ) ); } @@ -197,7 +269,7 @@ private Set getMappers(final Set annotations // on some JDKs, RoundEnvironment.getElementsAnnotatedWith( ... ) returns types with // annotations unknown to the compiler, even though they are not declared Mappers - if ( mapperTypeElement != null && MapperPrism.getInstanceOn( mapperTypeElement ) != null ) { + if ( mapperTypeElement != null && MapperGem.instanceOn( mapperTypeElement ) != null ) { mapperTypes.add( mapperTypeElement ); } } @@ -220,18 +292,26 @@ private void processMapperElements(Set mapperElements, RoundContext // of one outer interface List tst = mapperElement.getEnclosedElements(); ProcessorContext context = new DefaultModelElementProcessorContext( - processingEnv, options, roundContext, getDeclaredTypesNotToBeImported( mapperElement ) + 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( mapperElement ); + deferredMappers.add( new DeferredMapper( mapperElement, erroneousElement ) ); } catch ( Throwable t ) { handleUncaughtError( mapperElement, t ); @@ -295,7 +375,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. @@ -315,14 +395,14 @@ private R process(ProcessorContext context, ModelElementProcessor p processors.add( processorIterator.next() ); } - Collections.sort( processors, new ProcessorComparator() ); + processors.sort( new ProcessorComparator() ); return processors; } private TypeElement asTypeElement(Element element) { return element.accept( - new ElementKindVisitor6() { + new ElementKindVisitor8() { @Override public TypeElement visitTypeAsInterface(TypeElement e, Void p) { return e; @@ -337,14 +417,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 e903c00e59..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 @@ -6,9 +6,11 @@ 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.Strings; @@ -19,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 { @@ -38,10 +40,8 @@ protected String getToExpression(ConversionContext conversionContext) { } private String dateTimeFormatter(ConversionContext conversionContext) { - if ( !Strings.isEmpty( conversionContext.getDateFormat() ) ) { - return ConversionUtils.dateTimeFormatter( conversionContext ) - + ".ofPattern( \"" + conversionContext.getDateFormat() - + "\" )"; + if ( Strings.isNotEmpty( conversionContext.getDateFormat() ) ) { + return GetDateTimeFormatterField.getDateTimeFormatterFieldName( conversionContext.getDateFormat() ); } else { return ConversionUtils.dateTimeFormatter( conversionContext ) + "." + defaultFormatterSuffix(); @@ -88,4 +88,15 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon 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/BigDecimalToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigDecimalToStringConversion.java index d696cf5bb5..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 @@ -14,8 +14,9 @@ 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; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link BigDecimal} and {@link String}. @@ -45,7 +46,7 @@ public String getToExpression(ConversionContext conversionContext) { public String getFromExpression(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { StringBuilder sb = new StringBuilder(); - sb.append( "(" + bigDecimal( conversionContext ) + ") " ); + sb.append( "(" ).append( bigDecimal( conversionContext ) ).append( ") " ); appendDecimalFormatter( sb, conversionContext ); sb.append( ".parse( )" ); return sb.toString(); @@ -64,18 +65,31 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon public List getRequiredHelperMethods(ConversionContext conversionContext) { List helpers = new ArrayList<>(); if ( conversionContext.getNumberFormat() != null ) { - helpers.add( new CreateDecimalFormat( conversionContext.getTypeFactory() ) ); + helpers.add( new CreateDecimalFormat( + conversionContext.getTypeFactory(), + conversionContext.getLocale() != null + ) ); } return helpers; } private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { - sb.append( "createDecimalFormat( " ); + boolean withLocale = conversionContext.getLocale() != null; + sb.append( "createDecimalFormat" ); + if ( withLocale ) { + sb.append( "WithLocale" ); + } + sb.append( "( " ); if ( conversionContext.getNumberFormat() != null ) { sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); } + if ( withLocale ) { + sb.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + sb.append( conversionContext.getLocale() ); + sb.append( "\" )" ); + } sb.append( " )" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/BigIntegerToStringConversion.java index 9cb1cf41cb..540d89db58 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 @@ -15,9 +15,10 @@ 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; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigDecimal; import static org.mapstruct.ap.internal.conversion.ConversionUtils.bigInteger; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link BigInteger} and {@link String}. @@ -47,7 +48,7 @@ public String getToExpression(ConversionContext conversionContext) { public String getFromExpression(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { StringBuilder sb = new StringBuilder(); - sb.append( "( (" + bigDecimal( conversionContext ) + ") " ); + sb.append( "( (" ).append( bigDecimal( conversionContext ) ).append( ") " ); appendDecimalFormatter( sb, conversionContext ); sb.append( ".parse( )" ); sb.append( " ).toBigInteger()" ); @@ -72,18 +73,31 @@ protected Set getFromConversionImportTypes(ConversionContext conversionCon public List getRequiredHelperMethods(ConversionContext conversionContext) { List helpers = new ArrayList<>(); if ( conversionContext.getNumberFormat() != null ) { - helpers.add( new CreateDecimalFormat( conversionContext.getTypeFactory() ) ); + helpers.add( new CreateDecimalFormat( + conversionContext.getTypeFactory(), + conversionContext.getLocale() != null + ) ); } return helpers; } private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversionContext) { - sb.append( "createDecimalFormat( " ); + boolean withLocale = conversionContext.getLocale() != null; + sb.append( "createDecimalFormat" ); + if ( withLocale ) { + sb.append( "WithLocale" ); + } + sb.append( "( " ); if ( conversionContext.getNumberFormat() != null ) { sb.append( "\"" ); sb.append( conversionContext.getNumberFormat() ); sb.append( "\"" ); } + if ( withLocale ) { + sb.append( ", " ).append( locale( conversionContext ) ).append( ".forLanguageTag( \"" ); + sb.append( conversionContext.getLocale() ); + sb.append( "\" )" ); + } sb.append( " )" ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionProvider.java index 2ff73d3485..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 @@ -5,11 +5,14 @@ */ 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.common.Assignment; import org.mapstruct.ap.internal.model.common.ConversionContext; -import org.mapstruct.ap.internal.model.HelperMethod; +import org.mapstruct.ap.internal.model.common.FieldReference; /** * Implementations create inline {@link TypeConversion}s such as @@ -43,10 +46,23 @@ public interface ConversionProvider { Assignment from(ConversionContext conversionContext); /** + * Retrieves any helper methods required for creating the conversion. + * * @param conversionContext ConversionContext providing optional information required for creating the conversion. * * @return any helper methods when required. */ List getRequiredHelperMethods(ConversionContext conversionContext); + /** + * Retrieves any fields required for creating the conversion. + * + * @param conversionContext ConversionContext providing optional information required for creating the conversion. + * + * @return any fields when required. + */ + default List getRequiredHelperFields(ConversionContext conversionContext) { + return Collections.emptyList(); + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java index 8d2557b334..96960c4a11 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ConversionUtils.java @@ -7,10 +7,13 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URL; 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; @@ -18,6 +21,7 @@ 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; @@ -188,6 +192,17 @@ 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}. * @@ -231,4 +246,49 @@ public static String dateTimeFormatter(ConversionContext conversionContext) { 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.URL}. + * + * @param conversionContext Conversion context + * + * @return Name or fully-qualified name. + */ + public static String url(ConversionContext conversionContext) { + return typeReferenceName( conversionContext, URL.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 9f0ad97485..076cd2f42e 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 @@ -7,6 +7,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.net.URL; import java.sql.Time; import java.sql.Timestamp; import java.time.Duration; @@ -20,14 +21,16 @@ 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.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}. @@ -39,13 +42,15 @@ public class Conversions { 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 ); @@ -175,6 +180,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(); @@ -182,14 +188,20 @@ public Conversions(Elements elementUtils, TypeFactory typeFactory) { //misc. register( Enum.class, String.class, new EnumStringConversion() ); + register( Enum.class, Integer.class, new EnumToIntegerConversion() ); + register( Enum.class, int.class, new EnumToIntegerConversion() ); register( Date.class, String.class, new DateToStringConversion() ); register( BigDecimal.class, BigInteger.class, new BigDecimalToBigIntegerConversion() ); - register( Date.class, Time.class, new DateToSqlTimeConversion() ); - register( Date.class, java.sql.Date.class, new DateToSqlDateConversion() ); - register( Date.class, Timestamp.class, new DateToSqlTimestampConversion() ); + + registerJavaTimeSqlConversions(); // java.util.Currency <~> String register( Currency.class, String.class, new CurrencyToStringConversion() ); + + register( UUID.class, String.class, new UUIDToStringConversion() ); + register( Locale.class, String.class, new LocaleToStringConversion() ); + + registerURLConversion(); } private void registerJodaConversions() { @@ -223,28 +235,44 @@ private void registerJava8TimeConversions() { register( Period.class, String.class, new StaticParseToStringConversion() ); register( Duration.class, String.class, new StaticParseToStringConversion() ); - // Java 8 to Date + // 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( LocalDate.class, java.sql.Date.class, new JavaLocalDateToSqlDateConversion() ); register( Instant.class, Date.class, new JavaInstantToDateConversion() ); + // Java 8 time + register( LocalDateTime.class, LocalDate.class, new JavaLocalDateTimeToLocalDateConversion() ); + + } + + 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 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 +306,22 @@ private void registerBigDecimalConversion(Class targetType) { } } + private void registerURLConversion() { + if ( isJavaURLAvailable() ) { + register( URL.class, String.class, new URLToStringConversion() ); + } + } + + private boolean isJavaURLAvailable() { + 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 +329,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 +420,12 @@ public boolean equals(Object obj) { return false; } Key other = (Key) obj; - if ( sourceType == null ) { - if ( other.sourceType != null ) { - return false; - } - } - else if ( !sourceType.equals( other.sourceType ) ) { - return false; - } - if ( targetType == null ) { - if ( other.targetType != null ) { - return false; - } - } - else if ( !targetType.equals( other.targetType ) ) { + + if ( !Objects.equals( sourceType, other.sourceType ) ) { return false; } - return true; + + return Objects.equals( targetType, other.targetType ); } } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java index 9fe0c5b332..d1b49cff69 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.java @@ -5,16 +5,20 @@ */ package org.mapstruct.ap.internal.conversion; -import static org.mapstruct.ap.internal.util.Collections.asSet; - import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; import java.util.Set; import org.mapstruct.ap.internal.model.HelperMethod; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.model.source.MappingOptions; +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} @@ -27,13 +31,30 @@ public class CreateDecimalFormat extends HelperMethod { private final Parameter parameter; + private final Parameter localeParameter; private final Type returnType; private final Set importTypes; - public CreateDecimalFormat(TypeFactory typeFactory) { + public CreateDecimalFormat(TypeFactory typeFactory, boolean withLocale) { this.parameter = new Parameter( "numberFormat", typeFactory.getType( String.class ) ); + this.localeParameter = withLocale ? new Parameter( "locale", typeFactory.getType( Locale.class ) ) : null; this.returnType = typeFactory.getType( DecimalFormat.class ); - this.importTypes = asSet( parameter.getType(), returnType ); + if ( withLocale ) { + this.importTypes = asSet( + parameter.getType(), + returnType, + typeFactory.getType( DecimalFormatSymbols.class ), + typeFactory.getType( Locale.class ) + ); + } + else { + this.importTypes = asSet( parameter.getType(), returnType ); + } + } + + @Override + public String getName() { + return localeParameter == null ? "createDecimalFormat" : "createDecimalFormatWithLocale"; } @Override @@ -52,7 +73,20 @@ public Type getReturnType() { } @Override - public MappingOptions getMappingOptions() { - return MappingOptions.empty(); + 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 index 99797f898c..3f32994734 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/conversion/CurrencyToStringConversion.java @@ -15,6 +15,8 @@ import static org.mapstruct.ap.internal.conversion.ConversionUtils.currency; /** + * Conversion between {@link Currency} and {@link String}. + * * @author Darren Rambaud */ public class CurrencyToStringConversion extends SimpleConversion { 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 eff3731c85..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 @@ -10,6 +10,8 @@ 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.HelperMethod; import org.mapstruct.ap.internal.model.TypeConversion; @@ -17,9 +19,9 @@ 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.util.Collections.asSet; +import static org.mapstruct.ap.internal.conversion.ConversionUtils.locale; import static org.mapstruct.ap.internal.conversion.ConversionUtils.simpleDateFormat; +import static org.mapstruct.ap.internal.util.Collections.asSet; /** * Conversion between {@link String} and {@link Date}. @@ -30,16 +32,16 @@ public class DateToStringConversion implements ConversionProvider { @Override public Assignment to(ConversionContext conversionContext) { - return new TypeConversion( asSet( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ), - Collections.emptyList(), + return new TypeConversion( getImportTypes( conversionContext ), + Collections.emptyList(), getConversionExpression( conversionContext, "format" ) ); } @Override public Assignment from(ConversionContext conversionContext) { - return new TypeConversion( asSet( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ), - asList( conversionContext.getTypeFactory().getType( ParseException.class ) ), + return new TypeConversion( getImportTypes( conversionContext ), + Collections.singletonList( conversionContext.getTypeFactory().getType( ParseException.class ) ), getConversionExpression( conversionContext, "parse" ) ); } @@ -49,6 +51,17 @@ public List getRequiredHelperMethods(ConversionContext conversionC return Collections.emptyList(); } + private Set getImportTypes(ConversionContext conversionContext) { + if ( conversionContext.getLocale() == null ) { + return Collections.singleton( conversionContext.getTypeFactory().getType( SimpleDateFormat.class ) ); + } + + return asSet( + conversionContext.getTypeFactory().getType( SimpleDateFormat.class ), + conversionContext.getTypeFactory().getType( Locale.class ) + ); + } + private String getConversionExpression(ConversionContext conversionContext, String method) { StringBuilder conversionString = new StringBuilder( "new " ); conversionString.append( simpleDateFormat( conversionContext ) ); @@ -57,7 +70,16 @@ private String getConversionExpression(ConversionContext conversionContext, Stri 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( ")." ); 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..b43dc4a48f --- /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 static org.mapstruct.ap.internal.util.Collections.asSet; + +import java.util.Set; + +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +/** + * 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/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/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/PrimitiveToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/PrimitiveToStringConversion.java index fcc7241290..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 @@ -6,7 +6,9 @@ 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; @@ -15,6 +17,9 @@ 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 @@ -53,9 +58,15 @@ public String getToExpression(ConversionContext conversionContext) { @Override public Set getToConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + 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(); @@ -80,9 +91,15 @@ public String getFromExpression(ConversionContext conversionContext) { @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + 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(); @@ -97,6 +114,16 @@ private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversi 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/ReverseConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/ReverseConversion.java index 30fe24fa34..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 @@ -5,14 +5,15 @@ */ 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.common.Assignment; import org.mapstruct.ap.internal.model.common.ConversionContext; -import org.mapstruct.ap.internal.model.HelperMethod; +import org.mapstruct.ap.internal.model.common.FieldReference; /** - * 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 @@ -21,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 ); } @@ -41,7 +42,11 @@ public Assignment from(ConversionContext conversionContext) { @Override public List getRequiredHelperMethods(ConversionContext conversionContext) { - return Collections.emptyList(); + 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 c143085bbc..f0e0ccd880 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 @@ -45,7 +45,6 @@ public List getRequiredHelperMethods(ConversionContext conversionC 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. 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/URLToStringConversion.java b/processor/src/main/java/org/mapstruct/ap/internal/conversion/URLToStringConversion.java new file mode 100644 index 0000000000..716dfb3bb2 --- /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 org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.Type; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Set; + +import static org.mapstruct.ap.internal.conversion.ConversionUtils.url; +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 "new " + url( conversionContext ) + "( )"; + } + + @Override + protected Set getFromConversionImportTypes(final ConversionContext conversionContext) { + return asSet( conversionContext.getTypeFactory().getType( URL.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 300c901216..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 @@ -6,7 +6,9 @@ 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; @@ -15,6 +17,9 @@ 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}. @@ -52,9 +57,15 @@ public String getToExpression(ConversionContext conversionContext) { @Override public Set getToConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + 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(); @@ -79,9 +90,15 @@ public String getFromExpression(ConversionContext conversionContext) { @Override protected Set getFromConversionImportTypes(ConversionContext conversionContext) { if ( requiresDecimalFormat( conversionContext ) ) { - return Collections.singleton( - conversionContext.getTypeFactory().getType( DecimalFormat.class ) - ); + 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(); @@ -96,6 +113,16 @@ private void appendDecimalFormatter(StringBuilder sb, ConversionContext conversi 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/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..a8b62babe9 --- /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.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.Ignored; +import org.mapstruct.IgnoredList; +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 index 8d8750b9fd..584e0260bc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractBaseBuilder.java @@ -5,15 +5,15 @@ */ 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.ForgedMethod; import org.mapstruct.ap.internal.model.source.MappingMethodUtils; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Message; /** @@ -40,7 +40,7 @@ public B method(Method sourceMethod) { } /** - * Checks if MapStruct is allowed to generate an automatic sub-mapping between {@code sourceType} and @{code + * Checks if MapStruct is allowed to generate an automatic sub-mapping between {@code sourceType} and {@code * targetType}. * This will evaluate to {@code true}, when: *
    11. @@ -60,13 +60,12 @@ boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType) { } private boolean isDisableSubMappingMethodsGeneration() { - MapperConfiguration configuration = MapperConfiguration.getInstanceOn( ctx.getMapperTypeElement() ); - return configuration.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, then this method used for the assignment. + * 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} @@ -75,30 +74,40 @@ private boolean isDisableSubMappingMethodsGeneration() { */ Assignment createForgedAssignment(SourceRHS sourceRHS, ForgedMethod forgedMethod) { - if ( ctx.getForgedMethodsUnderCreation().containsKey( forgedMethod ) ) { - return createAssignment( sourceRHS, ctx.getForgedMethodsUnderCreation().get( forgedMethod ) ); - } - else { - ctx.getForgedMethodsUnderCreation().put( forgedMethod, forgedMethod ); - } - - MappingMethod forgedMappingMethod; + Supplier forgedMappingMethodCreator; if ( MappingMethodUtils.isEnumMapping( forgedMethod ) ) { - forgedMappingMethod = new ValueMappingMethod.Builder() + forgedMappingMethodCreator = () -> new ValueMappingMethod.Builder() .method( forgedMethod ) - .valueMappings( forgedMethod.getMappingOptions().getValueMappings() ) + .valueMappings( forgedMethod.getOptions().getValueMappings() ) + .enumMapping( forgedMethod.getOptions().getEnumMappingOptions() ) .mappingContext( ctx ) .build(); } else { - forgedMappingMethod = new BeanMappingMethod.Builder() + 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; } @@ -106,7 +115,7 @@ Assignment createForgedAssignment(SourceRHS source, ForgedMethod methodRef, Mapp if ( mappingMethod == null ) { return null; } - if (methodRef.getMappingOptions().isRestrictToDefinedMappings() || + 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 @@ -145,10 +154,10 @@ void reportCannotCreateMapping(Method method, String sourceErrorMessagePart, Typ method.getExecutable(), Message.PROPERTYMAPPING_MAPPING_NOT_FOUND, sourceErrorMessagePart, - targetType, + targetType.describe(), targetPropertyName, - targetType, - sourceType + targetType.describe(), + sourceType.describe() ); } @@ -170,10 +179,10 @@ void reportCannotCreateMapping(Method method, AnnotationMirror posHint, String s posHint, Message.PROPERTYMAPPING_MAPPING_NOT_FOUND, sourceErrorMessagePart, - targetType, + targetType.describe(), targetPropertyName, - targetType, - sourceType + 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 index 759f22ca73..80b900bb57 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AbstractMappingMethodBuilder.java @@ -5,33 +5,74 @@ */ package org.mapstruct.ap.internal.model; +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.ForgedMethod; -import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; +import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.util.Strings; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * 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 { + 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; } @@ -42,26 +83,18 @@ Assignment forgeMapping(SourceRHS sourceRHS, Type sourceType, Type targetType) { if ( method instanceof ForgedMethod ) { history = ( (ForgedMethod) method ).getHistory(); } - ForgedMethod forgedMethod = new ForgedMethod( - name, - sourceType, + + description = new ForgedMethodHistory( + history, + Strings.stubPropertyName( sourceRHS.getSourceType().getName() ), + Strings.stubPropertyName( targetType.getName() ), + sourceRHS.getSourceType(), targetType, - method.getMapperConfiguration(), - method.getExecutable(), - method.getContextParameters(), - method.getContextProvidedMethods(), - new ForgedMethodHistory( - history, - Strings.stubPropertyName( sourceRHS.getSourceType().getName() ), - Strings.stubPropertyName( targetType.getName() ), - sourceRHS.getSourceType(), - targetType, - shouldUsePropertyNamesInHistory(), - sourceRHS.getSourceErrorMessagePart() - ), - null, - true - ); + shouldUsePropertyNamesInHistory(), + sourceRHS.getSourceErrorMessagePart() ); + + ForgedMethod forgedMethod = + forgeMethodCreator.createMethod( name, sourceType, targetType, method, description, true ); return createForgedAssignment( sourceRHS, forgedMethod ); } @@ -80,4 +113,28 @@ private String getName(Type type) { 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 index 1c38b1062b..889d602cdb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/AnnotatedConstructor.java @@ -36,14 +36,14 @@ public static AnnotatedConstructor forComponentModels(String name, if ( constructor instanceof NoArgumentConstructor ) { noArgumentConstructor = (NoArgumentConstructor) constructor; } - NoArgumentConstructor noArgConstructorToInBecluded = null; + NoArgumentConstructor noArgConstructorToBeIncluded = null; Set fragmentsToBeIncluded = Collections.emptySet(); if ( includeNoArgConstructor ) { if ( noArgumentConstructor != null ) { - noArgConstructorToInBecluded = noArgumentConstructor; + noArgConstructorToBeIncluded = noArgumentConstructor; } else { - noArgConstructorToInBecluded = new NoArgumentConstructor( name, fragmentsToBeIncluded ); + noArgConstructorToBeIncluded = new NoArgumentConstructor( name, fragmentsToBeIncluded ); } } else if ( noArgumentConstructor != null ) { @@ -53,7 +53,7 @@ else if ( noArgumentConstructor != null ) { name, mapperReferences, annotations, - noArgConstructorToInBecluded, + noArgConstructorToBeIncluded, fragmentsToBeIncluded ); } 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 efc3fa0730..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 @@ -6,9 +6,11 @@ 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; @@ -21,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; } @@ -41,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/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index dc4c005bb7..d67b550ad4 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 @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.util.Collections.first; - import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; @@ -16,41 +14,72 @@ 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.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.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.BeanMapping; -import org.mapstruct.ap.internal.model.source.ForgedMethod; -import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; -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.PropertyEntry; 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.model.source.TargetReference; -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.prism.ReportingPolicyPrism; -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.ExecutableElementAccessor; +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,226 +91,614 @@ 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 Type resultType; + private final List constructorConstantMappings; + private final List subclassMappings; + private final Type returnTypeToConstruct; + 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 { + public static class Builder extends AbstractMappingMethodBuilder { - private MappingBuilderContext ctx; - private Method method; + /* 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 NullValueMappingStrategyPrism nullValueMappingStrategy; - private SelectionParameters selectionParameters; private final Set existingVariableNames = new HashSet<>(); - private Map> methodMappings; - private SingleMappingByTargetPropertyNameFunction singleMapping; - private final Map> unprocessedDefinedTargets = new HashMap<>(); + private final Map> unprocessedDefinedTargets = new LinkedHashMap<>(); + private final Map sourceParametersReassignments = new HashMap<>(); - public Builder mappingContext(MappingBuilderContext mappingContext) { - this.ctx = mappingContext; - return this; + private MappingReferences mappingReferences; + private List targetThisReferences; + private MethodReference factoryMethod; + private boolean hasFactoryMethod; + + public Builder() { + super( Builder.class ); + } + + @Override + protected boolean shouldUsePropertyNamesInHistory() { + return true; } public Builder sourceMethod(SourceMethod sourceMethod) { - singleMapping = new SourceMethodSingleMapping( sourceMethod ); - return setupMethodWithMapping( sourceMethod ); + method( sourceMethod ); + return this; } - public Builder forgedMethod(Method method) { - singleMapping = new EmptySingleMapping(); - return setupMethodWithMapping( method ); + 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; } - private Builder setupMethodWithMapping(Method sourceMethod) { - this.method = sourceMethod; - this.methodMappings = sourceMethod.getMappingOptions().getMappings(); - CollectionMappingStrategyPrism cms = sourceMethod.getMapperConfiguration().getCollectionMappingStrategy(); - Type mappingType = method.getResultType(); - if ( !method.isUpdateMethod() ) { - mappingType = mappingType.getEffectiveType(); + @Override + public BeanMappingMethod build() { + + 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 accessors = mappingType - .getPropertyWriteAccessors( cms ); - this.targetProperties = accessors.keySet(); + /* the type that needs to be used in the mapping process as target */ + Type resultTypeToMap = returnTypeToConstruct == null ? method.getResultType() : returnTypeToConstruct; + + existingVariableNames.addAll( method.getParameterNames() ); + + CollectionMappingStrategyGem cms = this.method.getOptions().getMapper().getCollectionMappingStrategy(); + + // determine accessors + Map accessors = resultTypeToMap.getPropertyWriteAccessors( cms ); + this.targetProperties = new LinkedHashSet<>( accessors.keySet() ); this.unprocessedTargetProperties = new LinkedHashMap<>( accessors ); + + boolean constructorAccessorHadError = false; + if ( !method.isUpdateMethod() && !hasFactoryMethod ) { + ConstructorAccessor constructorAccessor = getConstructorAccessor( resultTypeToMap ); + if ( constructorAccessor != null && !constructorAccessor.hasError ) { + + 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<>(); + } + this.unprocessedSourceProperties = new LinkedHashMap<>(); for ( Parameter sourceParameter : method.getSourceParameters() ) { unprocessedSourceParameters.add( sourceParameter ); - if ( sourceParameter.getType().isPrimitive() || sourceParameter.getType().isArrayType() ) { - continue; + 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 ) + ); } - Map readAccessors = sourceParameter.getType().getPropertyReadAccessors(); - for ( String key : readAccessors.keySet() ) { - unprocessedSourceProperties.put( key, readAccessors.get( key ) ); + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() || + sourceParameterType.isMapType() ) { + continue; } + + Map readAccessors = sourceParameterType.getPropertyReadAccessors(); + + unprocessedSourceProperties.putAll( readAccessors ); } - existingVariableNames.addAll( method.getParameterNames() ); - BeanMapping beanMapping = method.getMappingOptions().getBeanMapping(); - if ( beanMapping != null ) { + // 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() ) { - unprocessedSourceProperties.remove( ignoreUnmapped ); + // 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 ); + } } } - return this; - } + initializeMappingReferencesIfNeeded( resultTypeToMap ); - public BeanMappingMethod build() { - // map properties with mapping - boolean mappingErrorOccured = handleDefinedMappings(); - if ( mappingErrorOccured ) { - return null; + boolean shouldHandledDefinedMappings = shouldHandledDefinedMappings( resultTypeToMap ); + + + if ( shouldHandledDefinedMappings ) { + // map properties with mapping + boolean mappingErrorOccurred = handleDefinedMappings( resultTypeToMap ); + if ( mappingErrorOccurred ) { + return null; + } } - if ( !method.getMappingOptions().isRestrictToDefinedMappings() ) { + // 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(); - // report errors on unmapped properties - reportErrorForUnmappedTargetPropertiesIfRequired(); - reportErrorForUnmappedSourcePropertiesIfRequired(); + // Initialize unprocessed constructor properties + handleUnmappedConstructorProperties(); - // get bean mapping (when specified as annotation ) - BeanMapping beanMapping = method.getMappingOptions().getBeanMapping(); - BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); + // report errors on unmapped properties + if ( shouldHandledDefinedMappings ) { + reportErrorForUnmappedTargetPropertiesIfRequired( resultTypeToMap, constructorAccessorHadError ); + reportErrorForUnmappedSourcePropertiesIfRequired(); + } + reportErrorForMissingIgnoredSourceProperties(); + reportErrorForUnusedSourceParameters(); + reportErrorForRedundantIgnoredSourceProperties(); // mapNullToDefault - NullValueMappingStrategyPrism nullValueMappingStrategy = - beanMapping != null ? beanMapping.getNullValueMappingStrategy() : null; - boolean mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy ); + boolean mapNullToDefault = method.getOptions() + .getBeanMapping() + .getNullValueMappingStrategy() + .isReturnDefault(); + // sort + sortPropertyMappingsByDependencies(); - // selectionParameters - SelectionParameters selectionParameters = beanMapping != null ? beanMapping.getSelectionParameters() : null; + // before / after mappings + List beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods( + method, + resultTypeToMap, + selectionParameters, + ctx, + existingVariableNames + ); - // check if there's a factory method for the result type - MethodReference factoryMethod = null; - if ( !method.isUpdateMethod() ) { - factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( - method, - method.getResultType(), - selectionParameters, - ctx - ); - } + 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 there's no factory method, try the resultType in the @BeanMapping - Type resultType = null; - if ( factoryMethod == null ) { - if ( selectionParameters != null && selectionParameters.getResultType() != null ) { - resultType = ctx.getTypeFactory().getType( selectionParameters.getResultType() ).getEffectiveType(); - if ( resultType.isAbstract() ) { - ctx.getMessager().printMessage( - method.getExecutable(), - beanMappingPrism.mirror, - Message.BEANMAPPING_ABSTRACT, - resultType, - method.getResultType() - ); - } - else if ( !resultType.isAssignableTo( method.getResultType() ) ) { - ctx.getMessager().printMessage( - method.getExecutable(), - beanMappingPrism.mirror, - Message.BEANMAPPING_NOT_ASSIGNABLE, resultType, method.getResultType() - ); - } - else if ( !resultType.hasEmptyAccessibleContructor() ) { - ctx.getMessager().printMessage( - method.getExecutable(), - beanMappingPrism.mirror, - Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, - resultType - ); - } + if ( method instanceof ForgedMethod ) { + ForgedMethod forgedMethod = (ForgedMethod) method; + if ( factoryMethod != null ) { + forgedMethod.addThrownTypes( factoryMethod.getThrownTypes() ); } - else if ( !method.isUpdateMethod() && method.getReturnType().getEffectiveType().isAbstract() ) { - ctx.getMessager().printMessage( - method.getExecutable(), - Message.GENERAL_ABSTRACT_RETURN_TYPE, - method.getReturnType().getEffectiveType() - ); + for ( LifecycleCallbackMethodReference beforeMappingMethod : beforeMappingMethods ) { + forgedMethod.addThrownTypes( beforeMappingMethod.getThrownTypes() ); } - else if ( !method.isUpdateMethod() && - !method.getReturnType().getEffectiveType().hasEmptyAccessibleContructor() ) { - ctx.getMessager().printMessage( - method.getExecutable(), - Message.GENERAL_NO_SUITABLE_CONSTRUCTOR, - method.getReturnType().getEffectiveType() - ); + + for ( LifecycleCallbackMethodReference afterMappingMethod : afterMappingMethods ) { + forgedMethod.addThrownTypes( afterMappingMethod.getThrownTypes() ); } - } - sortPropertyMappingsByDependencies(); - List beforeMappingMethods = LifecycleMethodResolver.beforeMappingMethods( - method, - selectionParameters, - ctx, - existingVariableNames - ); - List afterMappingMethods = - LifecycleMethodResolver.afterMappingMethods( method, selectionParameters, ctx, existingVariableNames ); + 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 ); - if (factoryMethod != null && method instanceof ForgedMethod ) { - ( (ForgedMethod) method ).addThrownTypes( factoryMethod.getThrownTypes() ); + List subclasses = new ArrayList<>(); + for ( SubclassMappingOptions subclassMappingOptions : method.getOptions().getSubclassMappings() ) { + subclasses.add( createSubclassMapping( subclassMappingOptions ) ); } MethodReference finalizeMethod = null; - if ( shouldCallFinalizerMethod( resultType == null ? method.getResultType() : resultType ) ) { + 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 ); + } } + return new BeanMappingMethod( method, + getMethodAnnotations(), existingVariableNames, propertyMappings, factoryMethod, mapNullToDefault, - resultType, + returnTypeToConstruct, + returnTypeBuilder, beforeMappingMethods, afterMappingMethods, - finalizeMethod + beforeMappingReferencesWithFinalizedReturnType, + afterMappingReferencesWithFinalizedReturnType, + afterMappingReferencesWithOptionalReturnType, + finalizeMethod, + mappingReferences, + subclasses, + presenceChecksByParameter, + subclassExhaustiveExceptionType, + sourceParametersReassignments ); } - private boolean shouldCallFinalizerMethod(Type resultType) { - Type returnType = method.getReturnType(); - if ( returnType.isVoid() ) { + 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; } - Type mappingType = method.isUpdateMethod() ? resultType : resultType.getEffectiveType(); - if ( mappingType.isAssignableTo( returnType ) ) { + 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 returnType.getBuilderType() != null; + return returnTypeBuilder != null; } private MethodReference getFinalizerMethod() { return BuilderFinisherMethodResolver.getBuilderFinisherMethod( method, - method.getReturnType().getBuilderType(), + returnTypeBuilder, ctx ); } @@ -290,12 +707,12 @@ private MethodReference getFinalizerMethod() { * 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(); + 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(); + Entry> entry = iterator.next(); String propertyName = entry.getKey(); if ( !unprocessedTargetProperties.containsKey( propertyName ) ) { continue; @@ -308,23 +725,28 @@ private void handleUnprocessedDefinedTargets() { .name( propertyName ) .build(); - MappingOptions mappingOptions = extractAdditionalOptions( propertyName, true ); + ReadAccessor targetPropertyReadAccessor = + method.getResultType().getReadAccessor( propertyName, forceUpdateMethod ); + MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) - .targetWriteAccessor( unprocessedTargetProperties.get( propertyName ) ) - .targetReadAccessor( getTargetPropertyReadAccessor( propertyName ) ) - .targetPropertyName( propertyName ) + .target( + propertyName, + targetPropertyReadAccessor, + unprocessedTargetProperties.get( propertyName ) + ) .sourceReference( reference ) .existingVariableNames( existingVariableNames ) - .dependsOn( mappingOptions.collectNestedDependsOn() ) - .forgeMethodWithMappingOptions( mappingOptions ) + .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 ); @@ -335,6 +757,28 @@ private void handleUnprocessedDefinedTargets() { } } + 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. @@ -360,76 +804,508 @@ private void sortPropertyMappingsByDependencies() { ); } else { - Collections.sort( - propertyMappings, new Comparator() { - @Override - public int compare(PropertyMapping o1, PropertyMapping o2) { - return graphAnalyzer.getTraversalSequence( o1.getName() ) - - graphAnalyzer.getTraversalSequence( o2.getName() ); - } - } - ); + propertyMappings.sort( Comparator.comparingInt( propertyMapping -> + graphAnalyzer.getTraversalSequence( propertyMapping.getName() ) ) ); } } - /** - * Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the - * inverse mapping method. - *

      - * If a match is found between a defined source (constant, expression, ignore or source) the mapping is removed - * from the remaining target properties. - *

      - * It is furthermore checked whether the given mappings are correct. When an error occurs, the method continues - * in search of more problems. - */ - private boolean handleDefinedMappings() { - - boolean errorOccurred = false; - Set handledTargets = new HashSet<>(); + private boolean canResultTypeFromBeanMappingBeConstructed(Type resultType) { - // first we have to handle nested target mappings - if ( method.getMappingOptions().hasNestedTargetReferences() ) { - errorOccurred = handleDefinedNestedTargetMapping( handledTargets ); - } - - for ( Map.Entry> entry : methodMappings.entrySet() ) { - for ( Mapping mapping : entry.getValue() ) { - TargetReference targetReference = mapping.getTargetReference(); - if ( targetReference.isValid() ) { - String target = first( targetReference.getPropertyEntries() ).getFullName(); - if ( !handledTargets.contains( target ) ) { - if ( handleDefinedMapping( mapping, handledTargets ) ) { - errorOccurred = true; - } - } - if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) { - List sourceEntries = mapping.getSourceReference().getPropertyEntries(); - if ( !sourceEntries.isEmpty() ) { - String source = first( sourceEntries ).getFullName(); - unprocessedSourceProperties.remove( source ); - } - } - } - else { - errorOccurred = true; - } - } + 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; + } - // remove the remaining name based properties - for ( String handledTarget : handledTargets ) { - unprocessedTargetProperties.remove( handledTarget ); - unprocessedDefinedTargets.remove( handledTarget ); + 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; + } - return errorOccurred; + 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; } - private boolean handleDefinedNestedTargetMapping(Set handledTargets) { + /** + * 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; + } + + /** + * Iterates over all defined mapping methods ({@code @Mapping(s)}), either directly given or inherited from the + * inverse mapping method. + *

      + * If a match is found between a defined source (constant, expression, ignore or source) the mapping is removed + * from the remaining target properties. + *

      + * 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 handleDefinedMappings(Type resultTypeToMap) { + + boolean errorOccurred = false; + Set handledTargets = new HashSet<>(); + + // first we have to handle nested target mappings + if ( mappingReferences.hasNestedTargetReferences() ) { + errorOccurred = handleDefinedNestedTargetMapping( handledTargets, resultTypeToMap ); + } + + 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 ); + } + + return errorOccurred; + } + + 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(); @@ -437,24 +1313,24 @@ private boolean handleDefinedNestedTargetMapping(Set handledTargets) { propertyMappings.addAll( holder.getPropertyMappings() ); handledTargets.addAll( holder.getHandledTargets() ); // Store all the unprocessed defined targets. - for ( Entry> entry : holder.getUnprocessedDefinedTarget().entrySet() ) { + for ( Entry> entry : holder.getUnprocessedDefinedTarget() + .entrySet() ) { if ( entry.getValue().isEmpty() ) { continue; } - unprocessedDefinedTargets.put( entry.getKey().getName(), entry.getValue() ); + unprocessedDefinedTargets.put( entry.getKey(), entry.getValue() ); } return holder.hasErrorOccurred(); } - private boolean handleDefinedMapping(Mapping mapping, Set handledTargets) { - + private boolean handleDefinedMapping(MappingReference mappingRef, Type resultTypeToMap, + Set handledTargets) { boolean errorOccured = false; PropertyMapping propertyMapping = null; - TargetReference targetRef = mapping.getTargetReference(); - PropertyEntry targetProperty = first( targetRef.getPropertyEntries() ); - String propertyName = targetProperty.getName(); + TargetReference targetRef = mappingRef.getTargetReference(); + MappingOptions mapping = mappingRef.getMapping(); // unknown properties given via dependsOn()? for ( String dependency : mapping.getDependsOn() ) { @@ -470,85 +1346,301 @@ private boolean handleDefinedMapping(Mapping mapping, Set handledTargets } } - // check the mapping options - // its an ignored property mapping - if ( mapping.isIgnored() ) { - propertyMapping = null; - handledTargets.add( mapping.getTargetName() ); + String targetPropertyName = first( targetRef.getPropertyEntries() ); + + // check if source / expression / constant are not somehow handled already + if ( unprocessedDefinedTargets.containsKey( targetPropertyName ) ) { + return false; } - // its a plain-old property mapping - else if ( mapping.getSourceName() != null ) { + 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; - // determine source parameter - SourceReference sourceRef = mapping.getSourceReference(); - if ( sourceRef.isValid() ) { + 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.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() + }; + } + 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() ); - // targetProperty == null can occur: we arrived here because we want as many errors - // as possible before we stop analysing - propertyMapping = new PropertyMappingBuilder() + propertyMapping = new JavaExpressionMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) - .targetProperty( targetProperty ) - .targetPropertyName( mapping.getTargetName() ) - .sourcePropertyName( mapping.getSourceName() ) - .sourceReference( sourceRef ) - .selectionParameters( mapping.getSelectionParameters() ) - .formattingParameters( mapping.getFormattingParameters() ) + .javaExpression( accessedType.getNull() ) .existingVariableNames( existingVariableNames ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .dependsOn( mapping.getDependsOn() ) - .defaultValue( mapping.getDefaultValue() ) - .defaultJavaExpression( mapping.getDefaultJavaExpression() ) .mirror( mapping.getMirror() ) - .nullValueCheckStrategy( mapping.getNullValueCheckStrategy() ) - .nullValuePropertyMappingStrategy( mapping.getNullValuePropertyMappingStrategy() ) .build(); - handledTargets.add( propertyName ); - unprocessedSourceParameters.remove( sourceRef.getParameter() ); - } - else { - errorOccured = true; } + handledTargets.add( targetPropertyName ); } - // its a constant + // 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 && !unprocessedDefinedTargets.containsKey( propertyName ) ) { + else if ( mapping.getConstant() != null ) { propertyMapping = new ConstantMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .constantExpression( mapping.getConstant() ) - .targetProperty( targetProperty ) - .targetPropertyName( mapping.getTargetName() ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .formattingParameters( mapping.getFormattingParameters() ) .selectionParameters( mapping.getSelectionParameters() ) + .options( mapping ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping.getDependsOn() ) .mirror( mapping.getMirror() ) .build(); - handledTargets.add( mapping.getTargetName() ); + handledTargets.add( targetPropertyName ); } - // its an expression + // 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 && !unprocessedDefinedTargets.containsKey( propertyName ) ) { + else if ( mapping.getJavaExpression() != null ) { propertyMapping = new JavaExpressionMappingBuilder() .mappingContext( ctx ) .sourceMethod( method ) .javaExpression( mapping.getJavaExpression() ) .existingVariableNames( existingVariableNames ) - .targetProperty( targetProperty ) - .targetPropertyName( mapping.getTargetName() ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .dependsOn( mapping.getDependsOn() ) .mirror( mapping.getMirror() ) .build(); - handledTargets.add( mapping.getTargetName() ); + 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; + } + // We can't break here since it is possible that the same property exists in multiple + // source parameters + sourceRef = matchingSourceRef; + } + } + + } + 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 ); + } + + if ( sourceRef != null ) { + // sourceRef == null is not considered an error here + if ( sourceRef.isValid() ) { + Parameter sourceParameter = sourceRef.getParameter(); + + // 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 ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) + .sourcePropertyName( mapping.getSourceName() ) + .sourceReference( sourceRef.withParameter( + sourceParametersReassignments.get( sourceParameter.getName() ) ) ) + .selectionParameters( mapping.getSelectionParameters() ) + .formattingParameters( mapping.getFormattingParameters() ) + .existingVariableNames( existingVariableNames ) + .dependsOn( mapping.getDependsOn() ) + .defaultValue( mapping.getDefaultValue() ) + .defaultJavaExpression( mapping.getDefaultJavaExpression() ) + .conditionJavaExpression( mapping.getConditionJavaExpression() ) + .mirror( mapping.getMirror() ) + .options( mapping ) + .build(); + 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; + + 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 + ); + } + } + } // remaining are the mappings without a 'source' so, 'only' a date format or qualifiers if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); @@ -557,6 +1649,41 @@ else if ( mapping.getJavaExpression() != null && !unprocessedDefinedTargets.cont 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() ) ); + } + } + /** * Iterates over all target properties and all source parameters. *

      @@ -564,91 +1691,62 @@ else if ( mapping.getJavaExpression() != null && !unprocessedDefinedTargets.cont * the set of remaining target properties. */ private void applyPropertyNameBasedMapping() { + 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 ); + } - Iterator> targetPropertyEntriesIterator = - unprocessedTargetProperties.entrySet().iterator(); - - while ( targetPropertyEntriesIterator.hasNext() ) { - - Entry targetProperty = targetPropertyEntriesIterator.next(); - String targetPropertyName = targetProperty.getKey(); - - PropertyMapping propertyMapping = null; - - if ( propertyMapping == null ) { - - for ( Parameter sourceParameter : method.getSourceParameters() ) { - - Type sourceType = sourceParameter.getType(); - - if ( sourceType.isPrimitive() || sourceType.isArrayType() ) { - continue; - } + /** + * 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; + } - PropertyMapping newPropertyMapping = null; - - Accessor sourceReadAccessor = - sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName ); - - ExecutableElementAccessor sourcePresenceChecker = - sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName ); - - if ( sourceReadAccessor != null ) { - Mapping mapping = singleMapping.getSingleMappingByTargetPropertyName( - targetProperty.getKey() ); - DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); - - SourceReference sourceRef = new SourceReference.BuilderFromProperty() - .sourceParameter( sourceParameter ) - .type( ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ) ) - .readAccessor( sourceReadAccessor ) - .presenceChecker( sourcePresenceChecker ) - .name( targetProperty.getKey() ) - .build(); - - newPropertyMapping = new PropertyMappingBuilder() - .mappingContext( ctx ) - .sourceMethod( method ) - .targetWriteAccessor( targetProperty.getValue() ) - .targetReadAccessor( getTargetPropertyReadAccessor( targetPropertyName ) ) - .targetPropertyName( targetPropertyName ) - .sourceReference( sourceRef ) - .formattingParameters( mapping != null ? mapping.getFormattingParameters() : null ) - .selectionParameters( mapping != null ? mapping.getSelectionParameters() : null ) - .defaultValue( mapping != null ? mapping.getDefaultValue() : null ) - .existingVariableNames( existingVariableNames ) - .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) - .forgeMethodWithMappingOptions( extractAdditionalOptions( targetPropertyName, false ) ) - .nullValueCheckStrategy( mapping != null ? mapping.getNullValueCheckStrategy() : null ) - .nullValuePropertyMappingStrategy( mapping != null ? - mapping.getNullValuePropertyMappingStrategy() : null ) - .mirror( mapping != null ? mapping.getMirror() : null ) - .build(); - - unprocessedSourceParameters.remove( sourceParameter ); - } + 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(); - if ( propertyMapping != null && newPropertyMapping != null ) { - // TODO improve error message - ctx.getMessager().printMessage( - method.getExecutable(), - Message.BEANMAPPING_SEVERAL_POSSIBLE_SOURCES, - targetPropertyName - ); - break; - } - else if ( newPropertyMapping != null ) { - propertyMapping = newPropertyMapping; - } - } - } + unprocessedSourceParameters.remove( sourceRef.getParameter() ); if ( propertyMapping != null ) { propertyMappings.add( propertyMapping ); - targetPropertyEntriesIterator.remove(); - unprocessedDefinedTargets.remove( targetPropertyName ); - unprocessedSourceProperties.remove( targetPropertyName ); } + unprocessedDefinedTargets.remove( targetPropertyName ); + unprocessedSourceProperties.remove( targetPropertyName ); } } @@ -667,29 +1765,24 @@ private void applyParameterNameBasedMapping() { Parameter sourceParameter = sourceParameters.next(); if ( sourceParameter.getName().equals( targetProperty.getKey() ) ) { - Mapping mapping = singleMapping.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 ) - .formattingParameters( mapping != null ? mapping.getFormattingParameters() : null ) - .selectionParameters( mapping != null ? mapping.getSelectionParameters() : null ) .existingVariableNames( existingVariableNames ) - .dependsOn( mapping != null ? mapping.getDependsOn() : Collections.emptyList() ) - .forgeMethodWithMappingOptions( extractAdditionalOptions( targetProperty.getKey(), false ) ) - .nullValueCheckStrategy( mapping != null ? mapping.getNullValueCheckStrategy() : null ) - .nullValuePropertyMappingStrategy( mapping != null ? - mapping.getNullValuePropertyMappingStrategy() : null ) - .mirror( mapping != null ? mapping.getMirror() : null ) + .forgeMethodWithMappingReferences( mappingRefs ) + .options( method.getOptions().getBeanMapping() ) .build(); propertyMappings.add( propertyMapping ); @@ -697,203 +1790,473 @@ private void applyParameterNameBasedMapping() { sourceParameters.remove(); unprocessedDefinedTargets.remove( targetProperty.getKey() ); unprocessedSourceProperties.remove( targetProperty.getKey() ); + unprocessedConstructorProperties.remove( targetProperty.getKey() ); + ignoreSourceProperties( sourceParameter ); } } } } - private MappingOptions extractAdditionalOptions(String targetProperty, boolean restrictToDefinedMappings) { - MappingOptions additionalOptions = null; - if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) { - - Map> mappings = new HashMap<>(); - for ( Mapping mapping : unprocessedDefinedTargets.get( targetProperty ) ) { - mappings.put( mapping.getTargetName(), Collections.singletonList( mapping ) ); + 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 ); } - additionalOptions = MappingOptions.forMappingsOnly( mappings, restrictToDefinedMappings ); } - return additionalOptions; } - private Accessor getTargetPropertyReadAccessor(String propertyName) { - return method.getResultType().getPropertyReadAccessors().get( propertyName ); - } + private SourceReference getSourceRefByTargetName(Parameter sourceParameter, String targetPropertyName) { + + SourceReference sourceRef = null; - private ReportingPolicyPrism getUnmappedTargetPolicy() { - MappingOptions mappingOptions = method.getMappingOptions(); - if ( mappingOptions.getBeanMapping() != null && - mappingOptions.getBeanMapping().getReportingPolicy() != null ) { - return mappingOptions.getBeanMapping().getReportingPolicy(); + Type sourceParameterType = sourceParameter.getType(); + Parameter sourceParameterToUse = sourceParameter; + if ( sourceParameterType.isOptionalType() ) { + sourceParameterType = sourceParameterType.getOptionalBaseType(); + sourceParameterToUse = sourceParametersReassignments.get( sourceParameter.getName() ); + } + if ( sourceParameterType.isPrimitive() || sourceParameterType.isArrayType() ) { + return sourceRef; } - MapperConfiguration mapperSettings = MapperConfiguration.getInstanceOn( ctx.getMapperTypeElement() ); + 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; + } - return mapperSettings.unmappedTargetPolicy( ctx.getOptions() ); + private MappingReferences extractMappingReferences(String targetProperty, boolean restrictToDefinedMappings) { + if ( unprocessedDefinedTargets.containsKey( targetProperty ) ) { + Set mappings = unprocessedDefinedTargets.get( targetProperty ); + return new MappingReferences( mappings, restrictToDefinedMappings ); + } + return null; } - private void reportErrorForUnmappedTargetPropertiesIfRequired() { + private ReportingPolicyGem getUnmappedTargetPolicy() { + if ( mappingReferences.isForForgedMethods() ) { + return ReportingPolicyGem.IGNORE; + } + // 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(Type resultType, + boolean constructorAccessorHadError) { // fetch settings from element to implement - ReportingPolicyPrism unmappedTargetPolicy = getUnmappedTargetPolicy(); + ReportingPolicyGem unmappedTargetPolicy = getUnmappedTargetPolicy(); - if ( method instanceof ForgedMethod && targetProperties.isEmpty() ) { - //TODO until we solve 1140 we report this error when the target properties are empty - 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, - targetType, - targetType, - sourceType - ); + 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 { - ForgedMethodHistory history = forgedMethod.getHistory(); + else if ( !constructorAccessorHadError ) { ctx.getMessager().printMessage( - this.method.getExecutable(), - Message.PROPERTYMAPPING_FORGED_MAPPING_WITH_HISTORY_NOT_FOUND, - history.createSourcePropertyErrorMessage(), - history.getTargetType(), - history.createTargetPropertyName(), - history.getTargetType(), - history.getSourceType() + method.getExecutable(), + Message.PROPERTYMAPPING_TARGET_HAS_NO_TARGET_PROPERTIES, + resultType.describe() ); } } else if ( !unprocessedTargetProperties.isEmpty() && unmappedTargetPolicy.requiresReport() ) { - Message msg = unmappedTargetPolicy.getDiagnosticKind() == Diagnostic.Kind.ERROR ? - Message.BEANMAPPING_UNMAPPED_TARGETS_ERROR : Message.BEANMAPPING_UNMAPPED_TARGETS_WARNING; + 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; + } + + reportErrorForUnmappedProperties( + unprocessedSourceProperties, + unmappedPropertiesMsg, + unmappedForgedPropertiesMsg ); + } + } + + 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; + } + } + + //CHECKSTYLE:OFF private BeanMappingMethod(Method method, + List annotations, Collection existingVariableNames, List propertyMappings, MethodReference factoryMethod, boolean mapNullToDefault, - Type resultType, + Type returnTypeToConstruct, + BuilderType returnTypeBuilder, List beforeMappingReferences, List afterMappingReferences, - MethodReference finalizerMethod) { + 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.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.resultType = resultType; + this.returnTypeToConstruct = returnTypeToConstruct; + this.subclassMappings = subclassMappings; + this.sourceParametersReassignments = sourceParametersReassignments; } - public List getPropertyMappings() { - return propertyMappings; + public Type getSubclassExhaustiveException() { + return subclassExhaustiveException; } public List getConstantMappings() { return constantMappings; } + public List getConstructorConstantMappings() { + return constructorConstantMappings; + } + + public List getSubclassMappings() { + return subclassMappings; + } + + 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.get( parameter.getName() ); + return mappingsByParameter.getOrDefault( parameter.getName(), Collections.emptyList() ); } - @Override - public Type getResultType() { - if ( resultType == null ) { - return super.getResultType(); - } - else { - return resultType; - } + 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 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() { @@ -906,38 +2269,90 @@ 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 ( !isExistingInstanceMapping() ) { - types.addAll( getResultType().getEffectiveType().getImportTypes() ); + if ( returnTypeToConstruct != null ) { + types.addAll( 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() ); } - if ( getResultType().getBuilderType() != null ) { - types.add( getResultType().getBuilderType().getOwningType() ); + 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 @@ -961,36 +2376,16 @@ public boolean equals(Object obj) { if ( !super.equals( obj ) ) { return false; } - return propertyMappings != null ? propertyMappings.equals( that.propertyMappings ) : - that.propertyMappings == null; - } - - private interface SingleMappingByTargetPropertyNameFunction { - Mapping getSingleMappingByTargetPropertyName(String targetPropertyName); - } - - private static class EmptySingleMapping implements SingleMappingByTargetPropertyNameFunction { - - @Override - public Mapping getSingleMappingByTargetPropertyName(String targetPropertyName) { - return null; + if ( !Objects.equals( propertyMappings, that.propertyMappings ) ) { + return false; } - } - - private static class SourceMethodSingleMapping implements SingleMappingByTargetPropertyNameFunction { - private final SourceMethod sourceMethod; - - private SourceMethodSingleMapping(SourceMethod sourceMethod) { - this.sourceMethod = sourceMethod; + if ( !Objects.equals( mappingReferences, that.mappingReferences ) ) { + return false; } - @Override - public Mapping getSingleMappingByTargetPropertyName(String targetPropertyName) { - return sourceMethod.getSingleMappingByTargetPropertyName( targetPropertyName ); - } - } + 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 index 78375d8e33..9b0507f399 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BuilderFinisherMethodResolver.java @@ -7,24 +7,39 @@ import java.util.Collection; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.VariableElement; import org.mapstruct.ap.internal.model.common.BuilderType; -import org.mapstruct.ap.internal.model.source.BeanMapping; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.prism.BuilderPrism; -import org.mapstruct.ap.internal.util.MapperConfiguration; +import org.mapstruct.ap.internal.gem.BuilderGem; +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() { } @@ -36,14 +51,14 @@ public static MethodReference getBuilderFinisherMethod(Method method, BuilderTyp return null; } - BuilderPrism builderMapping = builderMappingPrism( method, ctx ); - if ( builderMapping == null && buildMethods.size() == 1 ) { + 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 ( builderMapping != null ) { - buildMethodPattern = builderMapping.buildMethod(); + if ( builder != null ) { + buildMethodPattern = builder.buildMethod().get(); } for ( ExecutableElement buildMethod : buildMethods ) { String methodName = buildMethod.getSimpleName().toString(); @@ -52,25 +67,25 @@ public static MethodReference getBuilderFinisherMethod(Method method, BuilderTyp } } - if ( builderMapping == null ) { + if ( builder == null ) { ctx.getMessager().printMessage( method.getExecutable(), Message.BUILDER_NO_BUILD_METHOD_FOUND_DEFAULT, buildMethodPattern, builderType.getBuilder(), builderType.getBuildingType(), - Strings.join( buildMethods, ", " ) + Strings.join( buildMethods, ", ", EXECUTABLE_ELEMENT_NAME_EXTRACTOR ) ); } else { ctx.getMessager().printMessage( method.getExecutable(), - builderMapping.mirror, + builder.mirror(), Message.BUILDER_NO_BUILD_METHOD_FOUND, buildMethodPattern, builderType.getBuilder(), builderType.getBuildingType(), - Strings.join( buildMethods, ", " ) + Strings.join( buildMethods, ", ", EXECUTABLE_ELEMENT_NAME_EXTRACTOR ) ); } } @@ -78,11 +93,4 @@ public static MethodReference getBuilderFinisherMethod(Method method, BuilderTyp return null; } - private static BuilderPrism builderMappingPrism(Method method, MappingBuilderContext ctx) { - BeanMapping beanMapping = method.getMappingOptions().getBeanMapping(); - if ( beanMapping != null && beanMapping.getBuilder() != null ) { - return beanMapping.getBuilder(); - } - return MapperConfiguration.getInstanceOn( ctx.getMapperTypeElement() ).getBuilderPrism(); - } } 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 index ad9defb9d2..db9e012d78 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java @@ -5,24 +5,35 @@ */ 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.prism.CollectionMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; -import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT; -import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_NULL; +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. @@ -57,11 +68,11 @@ public class CollectionAssignmentBuilder { private Accessor targetReadAccessor; private Type targetType; private String targetPropertyName; - private PropertyMapping.TargetWriteAccessorType targetAccessorType; + private AccessorType targetAccessorType; private Assignment assignment; private SourceRHS sourceRHS; - private NullValueCheckStrategyPrism nvcs; - private NullValuePropertyMappingStrategyPrism nvpms; + private NullValueCheckStrategyGem nvcs; + private NullValuePropertyMappingStrategyGem nvpms; public CollectionAssignmentBuilder mappingBuilderContext(MappingBuilderContext ctx) { this.ctx = ctx; @@ -88,7 +99,7 @@ public CollectionAssignmentBuilder targetPropertyName(String targetPropertyName) return this; } - public CollectionAssignmentBuilder targetAccessorType(PropertyMapping.TargetWriteAccessorType targetAccessorType) { + public CollectionAssignmentBuilder targetAccessorType(AccessorType targetAccessorType) { this.targetAccessorType = targetAccessorType; return this; } @@ -113,12 +124,12 @@ public CollectionAssignmentBuilder rightHandSide(SourceRHS sourceRHS) { return this; } - public CollectionAssignmentBuilder nullValueCheckStrategy( NullValueCheckStrategyPrism nvcs ) { + public CollectionAssignmentBuilder nullValueCheckStrategy( NullValueCheckStrategyGem nvcs ) { this.nvcs = nvcs; return this; } - public CollectionAssignmentBuilder nullValuePropertyMappingStrategy( NullValuePropertyMappingStrategyPrism nvpms ) { + public CollectionAssignmentBuilder nullValuePropertyMappingStrategy( NullValuePropertyMappingStrategyGem nvpms ) { this.nvpms = nvpms; return this; } @@ -126,11 +137,10 @@ public CollectionAssignmentBuilder nullValuePropertyMappingStrategy( NullValuePr public Assignment build() { Assignment result = assignment; - CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy(); - boolean targetImmutable = cms == CollectionMappingStrategyPrism.TARGET_IMMUTABLE || targetReadAccessor == null; + CollectionMappingStrategyGem cms = method.getOptions().getMapper().getCollectionMappingStrategy(); + boolean targetImmutable = cms == CollectionMappingStrategyGem.TARGET_IMMUTABLE || targetReadAccessor == null; - if ( targetAccessorType == PropertyMapping.TargetWriteAccessorType.SETTER || - targetAccessorType == PropertyMapping.TargetWriteAccessorType.FIELD ) { + if ( targetAccessorType == AccessorType.SETTER || targetAccessorType.isFieldAssignment() ) { if ( result.isCallingUpdateMethod() && !targetImmutable ) { @@ -149,11 +159,12 @@ public Assignment build() { result, method.getThrownTypes(), factoryMethod, - PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ), + targetAccessorType.isFieldAssignment(), targetType, true, nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + false ); } else if ( method.isUpdateMethod() && !targetImmutable ) { @@ -165,27 +176,54 @@ else if ( method.isUpdateMethod() && !targetImmutable ) { nvcs, nvpms, ctx.getTypeFactory(), - PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ) + targetAccessorType.isFieldAssignment() ); } - else if ( result.getType() == Assignment.AssignmentType.DIRECT || - nvcs == NullValueCheckStrategyPrism.ALWAYS ) { + else if ( method.isUpdateMethod() && nvpms == IGNORE ) { result = new SetterWrapperForCollectionsAndMapsWithNullCheck( result, method.getThrownTypes(), targetType, ctx.getTypeFactory(), - PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ) + targetAccessorType.isFieldAssignment() ); } - else { + 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, - PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ) + 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 ); } } @@ -203,11 +241,86 @@ else if ( result.getType() == Assignment.AssignmentType.DIRECT || result, method.getThrownTypes(), targetType, - PropertyMapping.TargetWriteAccessorType.isFieldAssignment( targetAccessorType ) + 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) { + 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/ContainerMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java index 13759f5c12..9e7e64fefa 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethod.java @@ -7,11 +7,14 @@ 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.presence.NullPresenceCheck; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.util.Strings; @@ -29,30 +32,48 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod { private final SelectionParameters selectionParameters; private final String index1Name; private final String index2Name; + private final Parameter sourceParameter; + private final PresenceCheck sourceParameterPresenceCheck; private IterableCreation iterableCreation; - ContainerMappingMethod(Method method, Collection existingVariables, Assignment parameterAssignment, + ContainerMappingMethod(Method method, List annotations, + Collection existingVariables, Assignment parameterAssignment, MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, List beforeMappingReferences, List afterMappingReferences, SelectionParameters selectionParameters) { - super( method, existingVariables, factoryMethod, mapNullToDefault, beforeMappingReferences, + super( method, annotations, existingVariables, factoryMethod, mapNullToDefault, beforeMappingReferences, afterMappingReferences ); this.elementAssignment = parameterAssignment; this.loopVariableName = loopVariableName; - this.selectionParameters = selectionParameters; + this.selectionParameters = selectionParameters != null ? selectionParameters : SelectionParameters.empty(); + this.index1Name = Strings.getSafeVariableName( "i", existingVariables ); this.index2Name = Strings.getSafeVariableName( "j", existingVariables ); - } - public Parameter getSourceParameter() { + Parameter sourceParameter = null; for ( Parameter parameter : getParameters() ) { if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { - return parameter; + 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 = new NullPresenceCheck( this.sourceParameter.getName() ); + + } + + public Parameter getSourceParameter() { + return sourceParameter; + } + + public PresenceCheck getSourceParameterPresenceCheck() { + return sourceParameterPresenceCheck; } public IterableCreation getIterableCreation() { @@ -117,12 +138,7 @@ public boolean equals(Object obj) { ContainerMappingMethod other = (ContainerMappingMethod) obj; - if ( this.selectionParameters != null ) { - if ( !this.selectionParameters.equals( other.selectionParameters ) ) { - return false; - } - } - else if ( other.selectionParameters != null ) { + if ( !Objects.equals( selectionParameters, other.selectionParameters ) ) { return false; } 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 index a9a501a49e..be08ac1ca4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ContainerMappingMethodBuilder.java @@ -5,8 +5,6 @@ */ 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; @@ -16,14 +14,16 @@ 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.source.ForgedMethod; 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.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; +import static org.mapstruct.ap.internal.util.Collections.first; + +import javax.lang.model.element.AnnotationMirror; + /** * Builder that can be used to build {@link ContainerMappingMethod}(s). * @@ -37,9 +37,9 @@ public abstract class ContainerMappingMethodBuilder selfType, String errorMessagePart) { super( selfType ); @@ -56,13 +56,13 @@ public B selectionParameters(SelectionParameters selectionParameters) { return myself; } - public B nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) { - this.nullValueMappingStrategy = nullValueMappingStrategy; + public B callingContextTargetPropertyName(String callingContextTargetPropertyName) { + this.callingContextTargetPropertyName = callingContextTargetPropertyName; return myself; } - public B callingContextTargetPropertyName(String callingContextTargetPropertyName) { - this.callingContextTargetPropertyName = callingContextTargetPropertyName; + public B positionHint(AnnotationMirror positionHint) { + this.positionHint = positionHint; return myself; } @@ -85,29 +85,21 @@ public final M build() { ); SelectionCriteria criteria = SelectionCriteria.forMappingMethods( selectionParameters, + method.getOptions().getIterableMapping().getMappingControl( ctx.getElementUtils() ), callingContextTargetPropertyName, false ); - Assignment assignment = ctx.getMappingResolver().getTargetAssignment( - method, + Assignment assignment = ctx.getMappingResolver().getTargetAssignment( method, + getDescription(), targetElementType, formattingParameters, criteria, sourceRHS, - null + positionHint, + () -> forge( sourceRHS, sourceElementType, targetElementType ) ); - if ( assignment == null && !criteria.hasQualfiers() ) { - assignment = forgeMapping( sourceRHS, sourceElementType, targetElementType ); - if ( assignment != null ) { - ctx.getMessager().note( 2, Message.ITERABLEMAPPING_CREATE_ELEMENT_NOTE, assignment ); - } - } - else { - ctx.getMessager().note( 2, Message.ITERABLEMAPPING_SELECT_ELEMENT_NOTE, assignment ); - } - if ( assignment == null ) { if ( method instanceof ForgedMethod ) { // leave messaging to calling property mapping @@ -116,7 +108,8 @@ public final M build() { else { reportCannotCreateMapping( method, - String.format( "%s \"%s\"", sourceRHS.getSourceErrorMessagePart(), sourceRHS.getSourceType() ), + String.format( "%s \"%s\"", sourceRHS.getSourceErrorMessagePart(), + sourceRHS.getSourceType().describe() ), sourceRHS.getSourceType(), targetElementType, "" @@ -124,6 +117,7 @@ public final M build() { } } else { + ctx.getMessager().note( 2, Message.ITERABLEMAPPING_SELECT_ELEMENT_NOTE, assignment ); if ( method instanceof ForgedMethod ) { ForgedMethod forgedMethod = (ForgedMethod) method; forgedMethod.addThrownTypes( assignment.getThrownTypes() ); @@ -132,14 +126,14 @@ public final M build() { assignment = getWrapper( assignment, method ); // mapNullToDefault - boolean mapNullToDefault = false; - if ( method.getMapperConfiguration() != null ) { - mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy ); - } + boolean mapNullToDefault = method.getOptions() + .getIterableMapping() + .getNullValueMappingStrategy() + .isReturnDefault(); MethodReference factoryMethod = null; if ( !method.isUpdateMethod() ) { - factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method, method.getResultType(), null, ctx ); + factoryMethod = ObjectFactoryMethodResolver.getFactoryMethod( method, null, ctx ); } Set existingVariables = new HashSet<>( method.getParameterNames() ); @@ -171,6 +165,14 @@ public final M build() { ); } + 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, 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 ce034837e8..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 @@ -7,16 +7,15 @@ import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.SortedSet; - -import javax.lang.model.element.ElementKind; 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; /** @@ -29,11 +28,13 @@ public class Decorator extends GeneratedType { public static class Builder extends GeneratedTypeBuilder { private TypeElement mapperElement; - private DecoratedWithPrism decoratorPrism; + private DecoratedWithGem decorator; private boolean hasDelegateConstructor; private String implName; private String implPackage; + private boolean suppressGeneratorTimestamp; + private Set customAnnotations; public Builder() { super( Builder.class ); @@ -44,8 +45,8 @@ public Builder mapperElement(TypeElement mapperElement) { return this; } - public Builder decoratorPrism(DecoratedWithPrism decoratorPrism) { - this.decoratorPrism = decoratorPrism; + public Builder decoratedWith(DecoratedWithGem decoratedGem) { + this.decorator = decoratedGem; return this; } @@ -64,18 +65,29 @@ public Builder implPackage(String implPackage) { return this; } + 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, Mapper.getFlatName( mapperElement ) ); - Type decoratorType = typeFactory.getType( decoratorPrism.value() ); + Type decoratorType = typeFactory.getType( decorator.value().get() ); DecoratorConstructor decoratorConstructor = new DecoratorConstructor( 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( @@ -83,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(); - addIfImportRequired( 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; } @@ -134,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/DefaultMapperReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/DefaultMapperReference.java index 924648368a..6ac0c1c74d 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 @@ -21,19 +21,22 @@ */ 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" ) ); } @@ -42,7 +45,7 @@ public static DefaultMapperReference getInstance(Type type, boolean isAnnotatedM otherMapperReferences ); - return new DefaultMapperReference( type, isAnnotatedMapper, importTypes, variableName ); + return new DefaultMapperReference( type, isAnnotatedMapper, isSingleton, importTypes, variableName ); } @Override @@ -53,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/EnumMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java deleted file mode 100644 index 53d2e5ec29..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/EnumMappingMethod.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 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 static org.mapstruct.ap.internal.util.Collections.first; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; - -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.SelectionParameters; -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; - -/** - * 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 sourceMethod(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_SOURCES, - enumConstant, - Strings.join( targetConstants, ", " ) - ); - } - } - - SelectionParameters selectionParameters = getSelecionParameters( 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 ); - - return new EnumMappingMethod( method, enumMappings, beforeMappingMethods, afterMappingMethods ); - } - - private static SelectionParameters getSelecionParameters(SourceMethod method, Types typeUtils) { - BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); - if ( beanMappingPrism != null ) { - List qualifiers = beanMappingPrism.qualifiedBy(); - List qualifyingNames = beanMappingPrism.qualifiedByName(); - TypeMirror resultType = beanMappingPrism.resultType(); - return new SelectionParameters( qualifiers, qualifyingNames, resultType, typeUtils ); - } - 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_SOURCES, - 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( getSourceParameters() ); - } -} 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 2f2df4b1fc..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 @@ -8,6 +8,7 @@ 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; @@ -113,8 +114,7 @@ public boolean equals(Object obj) { return false; } final Field other = (Field) obj; - return !( (this.variableName == null) ? - (other.variableName != null) : !this.variableName.equals( other.variableName ) ); + return Objects.equals( variableName, other.variableName ); } public static List getFieldNames(Set fields) { 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..2aa12687d8 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java @@ -0,0 +1,443 @@ +/* + * Copyright 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/source/ForgedMethodHistory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethodHistory.java similarity index 97% rename from processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethodHistory.java rename to processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethodHistory.java index 65c9408573..3463413705 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethodHistory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethodHistory.java @@ -3,7 +3,7 @@ * * 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; +package org.mapstruct.ap.internal.model; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.util.Strings; @@ -43,7 +43,7 @@ public Type getSourceType() { } public String createSourcePropertyErrorMessage() { - return conditionallyCapitalizedElementType() + " \"" + getSourceType() + " " + + return conditionallyCapitalizedElementType() + " \"" + getSourceType().describe() + " " + stripBrackets( getDottedSourceElement() ) + "\""; } 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 81ccc779a6..4b39e92273 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 @@ -10,15 +10,14 @@ import java.util.List; import java.util.SortedSet; import java.util.TreeSet; - import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; 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; @@ -31,11 +30,11 @@ public abstract class GeneratedType extends ModelElement { private static final String JAVA_LANG_PACKAGE = "java.lang"; - protected abstract static class GeneratedTypeBuilder { + protected abstract static class GeneratedTypeBuilder> { - private T myself; + private final T myself; protected TypeFactory typeFactory; - protected Elements elementUtils; + protected ElementUtils elementUtils; protected Options options; protected VersionInformation versionInformation; protected SortedSet extraImportedTypes; @@ -46,7 +45,7 @@ protected abstract static class GeneratedTypeBuilder methods) { 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; @@ -102,22 +99,21 @@ public T methods(List methods) { private final boolean generatedTypeAvailable; // CHECKSTYLE:OFF - protected GeneratedType(TypeFactory typeFactory, String packageName, String name, String superClassName, - String interfacePackage, String interfaceName, List methods, + 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.methods = new ArrayList<>(methods); this.fields = fields; - this.suppressGeneratorTimestamp = options.isSuppressGeneratorTimestamp(); + this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; this.suppressGeneratorVersionComment = options.isSuppressGeneratorVersionComment(); this.versionInformation = versionInformation; this.accessibility = accessibility; @@ -153,16 +149,8 @@ public String getName() { return name; } - public String getSuperClassName() { - return superClassName; - } - - public String getInterfacePackage() { - return interfacePackage; - } - - public String getInterfaceName() { - return interfaceName; + public Type getMapperDefinitionType() { + return mapperDefinitionType; } public List getAnnotations() { @@ -173,7 +161,7 @@ public void addAnnotation(Annotation annotation) { annotations.add( annotation ); } - public List getMethods() { + public List getMethods() { return methods; } @@ -214,7 +202,9 @@ public SortedSet getImportTypes() { SortedSet importedTypes = new TreeSet<>(); addIfImportRequired( importedTypes, generatedType ); - for ( MappingMethod mappingMethod : methods ) { + addIfImportRequired( importedTypes, mapperDefinitionType ); + + for ( GeneratedTypeMethod mappingMethod : methods ) { for ( Type type : mappingMethod.getImportTypes() ) { addIfImportRequired( importedTypes, type ); } @@ -229,7 +219,9 @@ public SortedSet getImportTypes() { } for ( Annotation annotation : annotations ) { - addIfImportRequired( importedTypes, annotation.getType() ); + for ( Type type : annotation.getImportTypes() ) { + addIfImportRequired( importedTypes, type ); + } } for ( Type extraImport : extraImportedTypes ) { @@ -261,13 +253,19 @@ public void removeConstructor() { constructor = null; } + public Javadoc getJavadoc() { + return null; + } + protected void addIfImportRequired(Collection collection, Type typeToAdd) { if ( typeToAdd == null ) { return; } - if ( needsImportDeclaration( typeToAdd ) ) { - collection.add( typeToAdd ); + for ( Type type : typeToAdd.getImportTypes() ) { + if ( needsImportDeclaration( type ) ) { + collection.add( type ); + } } } @@ -288,8 +286,15 @@ private boolean needsImportDeclaration(Type typeToAdd) { } if ( typeToAdd.getPackageName().equals( packageName ) ) { - if ( !typeToAdd.getTypeElement().getNestingKind().isNested() ) { - return false; + if ( typeToAdd.getTypeElement() != null ) { + if ( !typeToAdd.getTypeElement().getNestingKind().isNested() ) { + return false; + } + } + else if ( typeToAdd.getComponentType() != null ) { + if ( !typeToAdd.getComponentType().getTypeElement().getNestingKind().isNested() ) { + return false; + } } } } 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 index 72e062cca8..6ff85c585f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java @@ -10,7 +10,6 @@ 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; @@ -19,7 +18,6 @@ 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.MapperConfiguration; import org.mapstruct.ap.internal.util.Strings; /** @@ -52,7 +50,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(); } /** @@ -130,6 +128,11 @@ 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. @@ -173,14 +176,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 * @@ -238,11 +239,6 @@ public Type getDefiningType() { return null; } - @Override - public MapperConfiguration getMapperConfiguration() { - return null; - } - @Override public boolean isLifecycleCallbackMethod() { return false; 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 5310fa382d..10e64b7008 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 @@ -9,6 +9,7 @@ import java.util.Collection; import java.util.List; +import java.util.Set; import org.mapstruct.ap.internal.model.assignment.LocalVarWrapper; import org.mapstruct.ap.internal.model.assignment.SetterWrapper; @@ -56,6 +57,7 @@ protected IterableMappingMethod instantiateMappingMethod(Method method, Collecti List afterMappingMethods, SelectionParameters selectionParameters) { return new IterableMappingMethod( method, + getMethodAnnotations(), existingVariables, assignment, factoryMethod, @@ -68,13 +70,15 @@ protected IterableMappingMethod instantiateMappingMethod(Method method, Collecti } } - private IterableMappingMethod(Method method, Collection existingVariables, Assignment parameterAssignment, + private IterableMappingMethod(Method method, List annotations, + Collection existingVariables, Assignment parameterAssignment, MethodReference factoryMethod, boolean mapNullToDefault, String loopVariableName, List beforeMappingReferences, List afterMappingReferences, SelectionParameters selectionParameters) { super( method, + annotations, existingVariables, parameterAssignment, factoryMethod, @@ -86,6 +90,14 @@ private IterableMappingMethod(Method method, Collection existingVariable ); } + @Override + public Set getImportTypes() { + Set types = super.getImportTypes(); + + types.add( getSourceElementType() ); + return types; + } + public Type getSourceElementType() { Type sourceParameterType = getSourceParameter().getType(); 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..1afb5258a6 --- /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 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; + +/** + * 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/LifecycleCallbackMethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java index 10fb2d9c77..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 @@ -80,17 +80,11 @@ public String getTargetVariableName() { @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 boolean hasMappingTargetParameter() { - for ( ParameterBinding param : getParameterBindings() ) { - if ( param.isMappingTarget() ) { - return true; - } - } - - return false; + return getParameterBindings().stream().anyMatch( ParameterBinding::isMappingTarget ); } /** 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 index 64eee60517..ada9dac470 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -9,8 +9,11 @@ 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; @@ -18,7 +21,7 @@ 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.SelectionCriteria; +import org.mapstruct.ap.internal.model.source.selector.SelectionContext; /** * Factory for creating lists of appropriate {@link LifecycleCallbackMethodReference}s @@ -32,16 +35,62 @@ 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, @@ -60,6 +109,7 @@ public static List afterMappingMethods(Method MappingBuilderContext ctx, Set existingVariableNames) { return collectLifecycleCallbackMethods( method, + method.getResultType(), selectionParameters, filterAfterMappingMethods( getAllAvailableMethods( method, ctx.getSourceModel() ) ), ctx, @@ -78,33 +128,44 @@ private static List getAllAvailableMethods(Method method, List availableMethods = new ArrayList<>( methodsProvidedByParams.size() + sourceModelMethods.size() ); - for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) { - availableMethods.add( methodProvidedByParams ); - } + availableMethods.addAll( methodsProvidedByParams ); availableMethods.addAll( sourceModelMethods ); return availableMethods; } private static List collectLifecycleCallbackMethods( - Method method, SelectionParameters selectionParameters, List callbackMethods, + Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, MappingBuilderContext ctx, Set existingVariableNames) { + return collectLifecycleCallbackMethods( + method, + targetType, + selectionParameters, + callbackMethods, + ctx, + existingVariableNames, + Collections::emptyList + ); + } - MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); - - Type targetType = method.getResultType(); + private static List collectLifecycleCallbackMethods( + Method method, Type targetType, SelectionParameters selectionParameters, List callbackMethods, + MappingBuilderContext ctx, Set existingVariableNames, + Supplier> parameterBindingsProvider) { - if ( !method.isUpdateMethod() ) { - targetType = targetType.getEffectiveType(); - } + MethodSelectors selectors = + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() ); List> matchingMethods = selectors.getMatchingMethods( - method, callbackMethods, - Collections. emptyList(), - targetType, - SelectionCriteria.forLifecycleMethods( selectionParameters ) ); + SelectionContext.forLifecycleMethods( + method, + targetType, + selectionParameters, + ctx.getTypeFactory(), + parameterBindingsProvider + ) + ); return toLifecycleCallbackMethodRefs( method, @@ -146,24 +207,14 @@ private static List toLifecycleCallbackMethodR } private static List filterBeforeMappingMethods(List methods) { - List result = new ArrayList<>(); - for ( SourceMethod method : methods ) { - if ( method.isBeforeMappingMethod() ) { - result.add( method ); - } - } - - return result; + return methods.stream() + .filter( SourceMethod::isBeforeMappingMethod ) + .collect( Collectors.toList() ); } private static List filterAfterMappingMethods(List methods) { - List result = new ArrayList<>(); - for ( SourceMethod method : methods ) { - if ( method.isAfterMappingMethod() ) { - result.add( method ); - } - } - - return result; + 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 b08fea5775..42dbf826d0 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 @@ -15,13 +15,13 @@ 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.presence.NullPresenceCheck; 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.prism.NullValueMappingStrategyPrism; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; @@ -37,13 +37,14 @@ public class MapMappingMethod extends NormalTypeMappingMethod { private final Assignment keyAssignment; private final Assignment valueAssignment; + private final Parameter sourceParameter; + private final PresenceCheck sourceParameterPresenceCheck; private IterableCreation iterableCreation; public static class Builder extends AbstractMappingMethodBuilder { private FormattingParameters keyFormattingParameters; private FormattingParameters valueFormattingParameters; - private NullValueMappingStrategyPrism nullValueMappingStrategy; private SelectionParameters keySelectionParameters; private SelectionParameters valueSelectionParameters; @@ -71,11 +72,6 @@ public Builder valueFormattingParameters(FormattingParameters valueFormattingPar return this; } - public Builder nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) { - this.nullValueMappingStrategy = nullValueMappingStrategy; - return this; - } - public MapMappingMethod build() { List sourceTypeParams = @@ -88,28 +84,24 @@ public MapMappingMethod build() { SourceRHS keySourceRHS = new SourceRHS( "entry.getKey()", keySourceType, new HashSet<>(), "map key" ); - SelectionCriteria keyCriteria = - SelectionCriteria.forMappingMethods( keySelectionParameters, null, false ); + SelectionCriteria keyCriteria = SelectionCriteria.forMappingMethods( + keySelectionParameters, + method.getOptions().getMapMapping().getKeyMappingControl( ctx.getElementUtils() ), + null, + false + ); Assignment keyAssignment = ctx.getMappingResolver().getTargetAssignment( method, + getDescription(), keyTargetType, keyFormattingParameters, keyCriteria, keySourceRHS, - null + null, + () -> forge( keySourceRHS, keySourceType, keyTargetType, Message.MAPMAPPING_CREATE_KEY_NOTE ) ); - if ( keyAssignment == null && !keyCriteria.hasQualfiers( ) ) { - keyAssignment = forgeMapping( keySourceRHS, keySourceType, keyTargetType ); - if ( keyAssignment != null ) { - ctx.getMessager().note( 2, Message.MAPMAPPING_CREATE_KEY_NOTE, keyAssignment ); - } - } - else { - ctx.getMessager().note( 2, Message.MAPMAPPING_SELECT_KEY_NOTE, keyAssignment ); - } - if ( keyAssignment == null ) { if ( method instanceof ForgedMethod ) { // leave messaging to calling property mapping @@ -121,7 +113,7 @@ public MapMappingMethod build() { String.format( "%s \"%s\"", keySourceRHS.getSourceErrorMessagePart(), - keySourceRHS.getSourceType() + keySourceRHS.getSourceType().describe() ), keySourceRHS.getSourceType(), keyTargetType, @@ -129,6 +121,9 @@ public MapMappingMethod build() { ); } } + else { + ctx.getMessager().note( 2, Message.MAPMAPPING_SELECT_KEY_NOTE, keyAssignment ); + } // find mapping method or conversion for value Type valueSourceType = sourceTypeParams.get( 1 ).getTypeBound(); @@ -137,16 +132,21 @@ public MapMappingMethod build() { SourceRHS valueSourceRHS = new SourceRHS( "entry.getValue()", valueSourceType, new HashSet<>(), "map value" ); - SelectionCriteria valueCriteria = - SelectionCriteria.forMappingMethods( valueSelectionParameters, null, false ); + SelectionCriteria valueCriteria = SelectionCriteria.forMappingMethods( + valueSelectionParameters, + method.getOptions().getMapMapping().getValueMappingControl( ctx.getElementUtils() ), + null, + false ); Assignment valueAssignment = ctx.getMappingResolver().getTargetAssignment( method, + getDescription(), valueTargetType, valueFormattingParameters, valueCriteria, valueSourceRHS, - null + null, + () -> forge( valueSourceRHS, valueSourceType, valueTargetType, Message.MAPMAPPING_CREATE_VALUE_NOTE ) ); if ( method instanceof ForgedMethod ) { @@ -159,16 +159,6 @@ public MapMappingMethod build() { } } - if ( valueAssignment == null && !valueCriteria.hasQualfiers( ) ) { - valueAssignment = forgeMapping( valueSourceRHS, valueSourceType, valueTargetType ); - if ( valueAssignment != null ) { - ctx.getMessager().note( 2, Message.MAPMAPPING_CREATE_VALUE_NOTE, valueAssignment ); - } - } - else { - ctx.getMessager().note( 2, Message.MAPMAPPING_SELECT_VALUE_NOTE, valueAssignment ); - } - if ( valueAssignment == null ) { if ( method instanceof ForgedMethod ) { // leave messaging to calling property mapping @@ -180,7 +170,7 @@ public MapMappingMethod build() { String.format( "%s \"%s\"", valueSourceRHS.getSourceErrorMessagePart(), - valueSourceRHS.getSourceType() + valueSourceRHS.getSourceType().describe() ), valueSourceRHS.getSourceType(), valueTargetType, @@ -188,17 +178,18 @@ public MapMappingMethod build() { ); } } + else { + ctx.getMessager().note( 2, Message.MAPMAPPING_SELECT_VALUE_NOTE, valueAssignment ); + } // mapNullToDefault - boolean mapNullToDefault = false; - if ( method.getMapperConfiguration() != null ) { - mapNullToDefault = method.getMapperConfiguration().isMapToDefault( nullValueMappingStrategy ); - } + boolean mapNullToDefault = + method.getOptions().getMapMapping().getNullValueMappingStrategy().isReturnDefault(); MethodReference factoryMethod = null; if ( !method.isUpdateMethod() ) { factoryMethod = ObjectFactoryMethodResolver - .getFactoryMethod( method, method.getResultType(), null, ctx ); + .getFactoryMethod( method, null, ctx ); } keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes(), keyTargetType, false ); @@ -212,6 +203,7 @@ public MapMappingMethod build() { return new MapMappingMethod( method, + getMethodAnnotations(), existingVariables, keyAssignment, valueAssignment, @@ -222,6 +214,14 @@ public MapMappingMethod build() { ); } + 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; @@ -229,25 +229,38 @@ protected boolean shouldUsePropertyNamesInHistory() { } - private MapMappingMethod(Method method, Collection existingVariableNames, Assignment keyAssignment, + private MapMappingMethod(Method method, List annotations, + Collection existingVariableNames, Assignment keyAssignment, Assignment valueAssignment, MethodReference factoryMethod, boolean mapNullToDefault, List beforeMappingReferences, List afterMappingReferences) { - super( method, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences, + super( method, annotations, existingVariableNames, factoryMethod, mapNullToDefault, beforeMappingReferences, afterMappingReferences ); this.keyAssignment = keyAssignment; this.valueAssignment = valueAssignment; - } - - public Parameter getSourceParameter() { + Parameter sourceParameter = null; for ( Parameter parameter : getParameters() ) { if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) { - return parameter; + 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 = new NullPresenceCheck( this.sourceParameter.getName() ); + } + + public Parameter getSourceParameter() { + return sourceParameter; + } + + public PresenceCheck getSourceParameterPresenceCheck() { + return sourceParameterPresenceCheck; } public List getSourceElementTypes() { 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 0ebd1f0ffd..cd092ca4f4 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 @@ -8,9 +8,7 @@ import java.util.List; import java.util.Set; import java.util.SortedSet; - import javax.lang.model.element.Element; -import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; import org.mapstruct.ap.internal.model.common.Accessibility; @@ -43,6 +41,9 @@ public static class Builder extends GeneratedTypeBuilder { private boolean customName; private String implPackage; private boolean customPackage; + private boolean suppressGeneratorTimestamp; + private Set customAnnotations; + private Javadoc javadoc; public Builder() { super( Builder.class ); @@ -63,6 +64,11 @@ public Builder constructorFragments(Set fragment return this; } + public Builder additionalAnnotations(Set customAnnotations) { + this.customAnnotations = customAnnotations; + return this; + } + public Builder decorator(Decorator decorator) { this.decorator = decorator; return this; @@ -80,6 +86,16 @@ 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, getFlatName( element ) ) + ( decorator == null ? "" : "_" ); @@ -90,23 +106,27 @@ public Mapper build() { if ( !fragments.isEmpty() ) { constructor = new NoArgumentConstructor( implementationName, fragments ); } + + Type definitionType = typeFactory.getType( element ); + 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, + customAnnotations, methods, options, versionInformation, + suppressGeneratorTimestamp, Accessibility.fromModifiers( element.getModifiers() ), fields, constructor, decorator, - extraImportedTypes + extraImportedTypes, + javadoc ); } @@ -115,33 +135,38 @@ public Mapper build() { 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, String superClassName, - String interfacePackage, String interfaceName, boolean customPackage, boolean customImplName, + 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 ) { + Decorator decorator, SortedSet extraImportedTypes, Javadoc javadoc ) { super( typeFactory, packageName, name, - superClassName, - interfacePackage, - interfaceName, + 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() { @@ -156,6 +181,11 @@ public boolean hasCustomImplementation() { return customImplName || customPackage; } + @Override + public Javadoc getJavadoc() { + return javadoc; + } + @Override protected String getTemplateName() { return getTemplateNameForClass( GeneratedType.class ); @@ -163,6 +193,10 @@ protected String getTemplateName() { /** * 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)) { 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 bbbf69dbdc..1ebf99ffeb 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 @@ -11,25 +11,28 @@ 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.TypeElement; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; 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.ForgedMethod; 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.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; /** @@ -76,11 +79,13 @@ public interface MappingResolver { * returns a parameter assignment * * @param mappingMethod target mapping method + * @param description the description source * @param targetType return type to match * @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: *

        @@ -90,19 +95,25 @@ public interface MappingResolver { *
      1. null, no assignment found
      2. *
      */ - Assignment getTargetAssignment(Method mappingMethod, Type targetType, + Assignment getTargetAssignment(Method mappingMethod, ForgedMethodHistory description, Type targetType, FormattingParameters formattingParameters, SelectionCriteria criteria, SourceRHS sourceRHS, - AnnotationMirror positionHint); + AnnotationMirror positionHint, + Supplier forger); Set getUsedSupportedMappings(); + + 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 EnumMappingStrategy enumMappingStrategy; + private final Map enumTransformationStrategies; private final Options options; private final TypeElement mapperTypeElement; private final List sourceModel; @@ -112,11 +123,15 @@ Assignment getTargetAssignment(Method mappingMethod, Type targetType, 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, + EnumMappingStrategy enumMappingStrategy, + Map enumTransformationStrategies, Options options, MappingResolver mappingResolver, TypeElement mapper, @@ -126,13 +141,17 @@ public MappingBuilderContext(TypeFactory typeFactory, this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; + this.versionInformation = versionInformation; this.accessorNaming = accessorNaming; + 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. @@ -163,11 +182,11 @@ public TypeFactory getTypeFactory() { return typeFactory; } - public Elements getElementUtils() { + public ElementUtils getElementUtils() { return elementUtils; } - public Types getTypeUtils() { + public TypeUtils getTypeUtils() { return typeUtils; } @@ -175,10 +194,22 @@ public FormattingMessager getMessager() { return messager; } + public VersionInformation getVersionInformation() { + return versionInformation; + } + public AccessorNamingUtils getAccessorNaming() { return accessorNaming; } + public EnumMappingStrategy getEnumMappingStrategy() { + return enumMappingStrategy; + } + + public Map getEnumTransformationStrategies() { + return enumTransformationStrategies; + } + public Options getOptions() { return options; } @@ -220,6 +251,10 @@ 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 @@ -232,11 +267,18 @@ public boolean canGenerateAutoSubMappingBetween(Type sourceType, Type targetType } /** - * @param type that MapStruct wants to use to genrate an autoamtic sub-mapping for/from + * @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 4691c9cdf7..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 @@ -5,27 +5,28 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.util.Strings.getSafeVariableName; -import static org.mapstruct.ap.internal.util.Strings.join; - 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.getSafeVariableName; +import static org.mapstruct.ap.internal.util.Strings.join; + /** * A method implemented or referenced by a {@link Mapper} class. * * @author Gunnar Morling */ -public abstract class MappingMethod extends ModelElement { +public abstract class MappingMethod extends GeneratedTypeMethod { private final String name; private final List parameters; @@ -69,7 +70,7 @@ protected MappingMethod(Method method, List parameters, Collection parameters) { @@ -96,7 +97,11 @@ else if ( getResultType().isArrayType() ) { return name; } else { - String name = getSafeVariableName( getResultType().getName(), existingVarNames ); + Type resultType = getResultType(); + if ( resultType.isOptionalType() ) { + resultType = resultType.getOptionalBaseType(); + } + String name = getSafeVariableName( resultType.getName(), existingVarNames ); existingVarNames.add( name ); return name; } @@ -152,6 +157,16 @@ public Set getImportTypes() { 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() ); + } + return types; } @@ -174,10 +189,10 @@ 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 = @@ -227,15 +242,15 @@ public boolean equals(Object obj) { //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 ( this.parameters != other.parameters && - (this.parameters == null || !this.parameters.equals( other.parameters )) ) { + + if ( !Objects.equals( parameters, other.parameters ) ) { return false; } - if ( this.returnType != other.returnType && - (this.returnType == null || !this.returnType.equals( other.returnType )) ) { + + 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 b95ef372d7..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 @@ -6,9 +6,12 @@ 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; @@ -17,6 +20,7 @@ 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; @@ -55,7 +59,10 @@ public class MethodReference extends ModelElement implements 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. @@ -84,12 +91,15 @@ protected MethodReference(Method method, MapperReference declaringMapper, Parame imported.addAll( binding.getImportTypes() ); } - this.importTypes = Collections.unmodifiableSet( imported ); + 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; } private MethodReference(BuiltInMethod method, ConversionContext contextParam) { @@ -105,6 +115,9 @@ private MethodReference(BuiltInMethod method, ConversionContext contextParam) { 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) { @@ -120,6 +133,59 @@ private MethodReference(String name, Type definingType, boolean isStatic) { 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() { @@ -157,11 +223,11 @@ public void setAssignment( Assignment assignment ) { @Override public String getSourceReference() { - return assignment.getSourceReference(); + return assignment != null ? assignment.getSourceReference() : null; } @Override - public String getSourcePresenceCheckerReference() { + public PresenceCheck getSourcePresenceCheckerReference() { return assignment.getSourcePresenceCheckerReference(); } @@ -230,16 +296,27 @@ public Set 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; } @@ -271,6 +348,18 @@ 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; } @@ -305,22 +394,13 @@ public boolean equals(Object obj) { return false; } MethodReference other = (MethodReference) obj; - if ( declaringMapper == null ) { - if ( other.declaringMapper != null ) { - return false; - } - } - else if ( !declaringMapper.equals( other.declaringMapper ) ) { + if ( !Objects.equals( declaringMapper, other.declaringMapper ) ) { return false; } - if ( providingParameter == null ) { - if ( other.providingParameter != null ) { - return false; - } - } - else if ( !providingParameter.equals( other.providingParameter ) ) { + if ( !Objects.equals( providingParameter, other.providingParameter ) ) { return false; } + return true; } @@ -350,10 +430,19 @@ 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().toString() : ""; - String argument = getAssignment() != null ? getAssignment().toString() : getSourceReference(); + 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 ) 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 c004baebb7..ed841ba5bc 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 @@ -6,15 +6,21 @@ 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.ForgedMethod; -import org.mapstruct.ap.internal.model.source.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.Strings; -import org.mapstruct.ap.internal.util.ValueProvider; +import org.mapstruct.ap.internal.util.accessor.PresenceCheckAccessor; /** * This method is used to convert the nested properties as listed in propertyEntries into a method @@ -52,21 +58,104 @@ public Builder mappingContext(MappingBuilderContext mappingContext) { public NestedPropertyMappingMethod build() { 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; + } } final List thrownTypes = new ArrayList<>(); List safePropertyEntries = new ArrayList<>(); - for ( PropertyEntry propertyEntry : propertyEntries ) { + if ( sourceParameter == null ) { + throw new IllegalStateException( "Method " + method + " has no source parameter." ); + } + + String previousPropertyName = sourceParameter.getName(); + Type previousPropertyType = sourceParameter.getType(); + for ( int i = 0; i < propertyEntries.size(); i++ ) { + PropertyEntry propertyEntry = propertyEntries.get( i ); + PresenceCheck presenceCheck; + + 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 ) { + // If this is not the first property entry, + // then we might need to combine the presence check with a null check of the previous property + 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 ); - safePropertyEntries.add( new SafePropertyEntry( propertyEntry, safeName ) ); + 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(); } 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( ForgedMethod method, List sourcePropertyEntries ) { @@ -91,7 +180,10 @@ public List getPropertyEntries() { public Set getImportTypes() { Set types = super.getImportTypes(); for ( SafePropertyEntry propertyEntry : safePropertyEntries) { - types.add( propertyEntry.getType() ); + types.addAll( propertyEntry.getType().getImportTypes() ); + if ( propertyEntry.getPresenceChecker() != null ) { + types.addAll( propertyEntry.getPresenceChecker().getImportTypes() ); + } } return types; } @@ -142,32 +234,27 @@ public boolean equals( Object obj ) { public static class SafePropertyEntry { private final String safeName; - private final String readAccessorName; - private final String presenceCheckerName; + private final String source; + private final PresenceCheck presenceChecker; private final Type type; - public SafePropertyEntry(PropertyEntry entry, String safeName) { + public SafePropertyEntry(Type type, String safeName, String source, PresenceCheck presenceCheck) { this.safeName = safeName; - this.readAccessorName = ValueProvider.of( entry.getReadAccessor() ).getValue(); - if ( entry.getPresenceChecker() != null ) { - this.presenceCheckerName = entry.getPresenceChecker().getSimpleName().toString(); - } - else { - this.presenceCheckerName = null; - } - this.type = entry.getType(); + this.source = source; + this.presenceChecker = presenceCheck; + this.type = type; } public String getName() { return safeName; } - public String getAccessorName() { - return readAccessorName; + public String getSource() { + return source; } - public String getPresenceCheckerName() { - return presenceCheckerName; + public PresenceCheck getPresenceChecker() { + return presenceChecker; } public Type getType() { @@ -185,26 +272,25 @@ public boolean equals(Object o) { SafePropertyEntry that = (SafePropertyEntry) o; - if ( readAccessorName != null ? !readAccessorName.equals( that.readAccessorName ) : - that.readAccessorName != null ) { + if ( !Objects.equals( source, that.source ) ) { return false; } - if ( presenceCheckerName != null ? !presenceCheckerName.equals( that.presenceCheckerName ) : - that.presenceCheckerName != null ) { + if ( !Objects.equals( presenceChecker, that.presenceChecker ) ) { return false; } - if ( type != null ? !type.equals( that.type ) : that.type != null ) { + if ( !Objects.equals( type, that.type ) ) { return false; } + return true; } @Override public int hashCode() { - int result = readAccessorName != null ? readAccessorName.hashCode() : 0; - result = 31 * result + ( presenceCheckerName != null ? presenceCheckerName.hashCode() : 0 ); + 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 index 2cf9b258e4..f7b28b3f84 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -6,21 +6,28 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; -import java.util.HashMap; +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.source.Mapping; +import org.mapstruct.ap.internal.util.accessor.ReadAccessor; +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.model.source.PropertyEntry; -import org.mapstruct.ap.internal.model.source.SourceReference; -import org.mapstruct.ap.internal.model.source.TargetReference; -import org.mapstruct.ap.internal.util.Extractor; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; +import org.mapstruct.ap.internal.util.accessor.Accessor; import static org.mapstruct.ap.internal.util.Collections.first; @@ -32,33 +39,16 @@ */ public class NestedTargetPropertyMappingHolder { - private static final Extractor SOURCE_PARAM_EXTRACTOR = new - Extractor() { - @Override - public Parameter apply(SourceReference sourceReference) { - return sourceReference.getParameter(); - } - }; - - private static final Extractor PROPERTY_EXTRACTOR = new - Extractor() { - @Override - public PropertyEntry apply(SourceReference sourceReference) { - return sourceReference.getPropertyEntries().isEmpty() ? null : - first( sourceReference.getPropertyEntries() ); - } - }; - private final List processedSourceParameters; private final Set handledTargets; private final List propertyMappings; - private final Map> unprocessedDefinedTarget; + private final Map> unprocessedDefinedTarget; private final boolean errorOccurred; public NestedTargetPropertyMappingHolder( List processedSourceParameters, Set handledTargets, List propertyMappings, - Map> unprocessedDefinedTarget, boolean errorOccurred) { + Map> unprocessedDefinedTarget, boolean errorOccurred) { this.processedSourceParameters = processedSourceParameters; this.handledTargets = handledTargets; this.propertyMappings = propertyMappings; @@ -74,7 +64,7 @@ public List getProcessedSourceParameters() { } /** - * @return all the targets that were hanled + * @return all the targets that were handled */ public Set getHandledTargets() { return handledTargets; @@ -90,7 +80,7 @@ public List getPropertyMappings() { /** * @return a map of all the unprocessed defined targets that can be applied to name forged base methods */ - public Map> getUnprocessedDefinedTarget() { + public Map> getUnprocessedDefinedTarget() { return unprocessedDefinedTarget; } @@ -104,10 +94,19 @@ public boolean hasErrorOccurred() { 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; @@ -124,6 +123,16 @@ public Builder existingVariableNames(Set 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<>(); @@ -131,24 +140,23 @@ public NestedTargetPropertyMappingHolder build() { // 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( method.getMappingOptions() ); - Map> unprocessedDefinedTarget - = new LinkedHashMap<>(); + GroupedTargetReferences groupedByTP = groupByTargetReferences( ); + Map> unprocessedDefinedTarget = new LinkedHashMap<>(); - for ( Map.Entry> entryByTP : groupedByTP.poppedTargetReferences.entrySet() ) { - PropertyEntry targetProperty = entryByTP.getKey(); + 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.keySet().size() > 1; + 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 + for ( Map.Entry> entryByParam : groupedBySourceParam .groupedBySourceParameter.entrySet() ) { Parameter sourceParameter = entryByParam.getKey(); @@ -160,16 +168,24 @@ public NestedTargetPropertyMappingHolder build() { 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 + for ( Map.Entry> entryBySP : groupedSourceReferences .groupedBySourceReferences .entrySet() ) { PropertyEntry sourceEntry = entryBySP.getKey(); - boolean forceUpdateMethodOrNonNestedReferencesPresent = - multipleSourceParametersForTP || !groupedSourceReferences.nonNested.isEmpty(); // 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) @@ -179,24 +195,24 @@ public NestedTargetPropertyMappingHolder build() { // @Mapping(target = "vehicleInfo.images", source = "images") //}) // See Issue1269Test, Issue1247Test, AutomappingAndNestedTest for more info as well - MappingOptions sourceMappingOptions = MappingOptions.forMappingsOnly( - groupByTargetName( entryBySP.getValue() ), + 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.getName() ) + .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( - sourceMappingOptions, + sourceMappingRefs, targetProperty, sourceRef, forceUpdateMethodOrNonNestedReferencesPresent @@ -206,20 +222,18 @@ public NestedTargetPropertyMappingHolder build() { propertyMappings.add( propertyMapping ); } - handledTargets.add( entryByTP.getKey().getName() ); + handledTargets.add( entryByTP.getKey() ); } - // For the nonNested mappings (assymetric) Mappings we also forge mappings + // 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() ) { - MappingOptions nonNestedOptions = MappingOptions.forMappingsOnly( - groupByTargetName( groupedSourceReferences.nonNested ), - true - ); + MappingReferences mappingReferences = + new MappingReferences( groupedSourceReferences.nonNested, true ); SourceReference reference = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) - .name( targetProperty.getName() ) + .name( targetProperty ) .build(); boolean forceUpdateMethodForNonNested = @@ -229,7 +243,7 @@ public NestedTargetPropertyMappingHolder build() { // an update method. The reason is that they might be for the same reference and we should // use update for it PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( - nonNestedOptions, + mappingReferences, targetProperty, reference, forceUpdateMethodForNonNested @@ -239,7 +253,7 @@ public NestedTargetPropertyMappingHolder build() { propertyMappings.add( propertyMapping ); } - handledTargets.add( entryByTP.getKey().getName() ); + handledTargets.add( entryByTP.getKey() ); } handleSourceParameterMappings( @@ -258,7 +272,7 @@ public NestedTargetPropertyMappingHolder build() { handledTargets, propertyMappings, unprocessedDefinedTarget, - groupedByTP.errorOccurred + errorOccurred ); } @@ -270,22 +284,20 @@ public NestedTargetPropertyMappingHolder build() { * @param sourceParameter the source parameter that is used * @param forceUpdateMethod whether we need to force an update method */ - private void handleSourceParameterMappings(List sourceParameterMappings, PropertyEntry targetProperty, - Parameter sourceParameter, boolean forceUpdateMethod) { + 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 - MappingOptions nonNestedOptions = MappingOptions.forMappingsOnly( - new HashMap<>(), - false, - true - ); + MappingReferences nonNestedRefs = + new MappingReferences( Collections.emptySet(), false, true ); SourceReference reference = new SourceReference.BuilderFromProperty() .sourceParameter( sourceParameter ) - .name( targetProperty.getName() ) + .name( targetProperty ) .build(); PropertyMapping propertyMapping = createPropertyMappingForNestedTarget( - nonNestedOptions, + nonNestedRefs, targetProperty, reference, forceUpdateMethod @@ -295,13 +307,13 @@ private void handleSourceParameterMappings(List sourceParameterMappings propertyMappings.add( propertyMapping ); } - handledTargets.add( targetProperty.getName() ); + handledTargets.add( targetProperty ); } } /** - * The target references are popped. The {@code List<}{@link Mapping}{@code >} are keyed on the unique first - * entries of the target references. + * The target references are popped. The {@code List<}{@link MappingOptions}{@code >} are keyed on the unique + * first entries of the target references. * *

      *

      @@ -341,43 +353,34 @@ private void handleSourceParameterMappings(List sourceParameterMappings * } *

    12. * - * @param mappingOptions that need to be used to create the {@link GroupedTargetReferences} - * * @return See above */ - private GroupedTargetReferences groupByTargetReferences(MappingOptions mappingOptions) { - Map> mappings = mappingOptions.getMappings(); + private GroupedTargetReferences groupByTargetReferences( ) { // group all mappings based on the top level name before popping - Map> mappingsKeyedByProperty - = new LinkedHashMap<>(); - Map> singleTargetReferences - = new LinkedHashMap<>(); - boolean errorOccurred = false; - for ( List mapping : mappings.values() ) { - Mapping firstMapping = first( mapping ); - TargetReference targetReference = firstMapping.getTargetReference(); - if ( !targetReference.isValid() ) { - errorOccurred = true; + 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; } - PropertyEntry property = first( targetReference.getPropertyEntries() ); - Mapping newMapping = firstMapping.popTargetReference(); + String property = first( propertyEntries ); + MappingReference newMapping = mapping.popTargetReference(); if ( newMapping != null ) { // group properties on current name. - if ( !mappingsKeyedByProperty.containsKey( property ) ) { - mappingsKeyedByProperty.put( property, new ArrayList<>() ); - } - mappingsKeyedByProperty.get( property ).add( newMapping ); + mappingsKeyedByProperty.computeIfAbsent( property, propertyEntry -> new LinkedHashSet<>() ) + .add( newMapping ); } else { - if ( !singleTargetReferences.containsKey( property ) ) { - singleTargetReferences.put( property, new ArrayList<>() ); - } - singleTargetReferences.get( property ).add( firstMapping ); + singleTargetReferences.computeIfAbsent( property, propertyEntry -> new LinkedHashSet<>() ) + .add( mapping ); } } - return new GroupedTargetReferences( mappingsKeyedByProperty, singleTargetReferences, errorOccurred ); + return new GroupedTargetReferences( mappingsKeyedByProperty, singleTargetReferences ); } /** @@ -463,18 +466,16 @@ private GroupedTargetReferences groupByTargetReferences(MappingOptions mappingOp * property as the {@code mappings} * @return the split mapping options. */ - private GroupedBySourceParameters groupBySourceParameter(List mappings, - List singleTargetReferences) { + private GroupedBySourceParameters groupBySourceParameter(Set mappings, + Set singleTargetReferences) { - Map> mappingsKeyedByParameter = new LinkedHashMap<>(); - List appliesToAll = new ArrayList<>(); - for ( Mapping mapping : mappings ) { + Map> mappingsKeyedByParameter = new LinkedHashMap<>(); + Set appliesToAll = new LinkedHashSet<>(); + for ( MappingReference mapping : mappings ) { if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) { Parameter parameter = mapping.getSourceReference().getParameter(); - if ( !mappingsKeyedByParameter.containsKey( parameter ) ) { - mappingsKeyedByParameter.put( parameter, new ArrayList<>() ); - } - mappingsKeyedByParameter.get( parameter ).add( mapping ); + mappingsKeyedByParameter.computeIfAbsent( parameter, key -> new LinkedHashSet<>() ) + .add( mapping ); } else { appliesToAll.add( mapping ); @@ -484,15 +485,15 @@ private GroupedBySourceParameters groupBySourceParameter(List mappings, populateWithSingleTargetReferences( mappingsKeyedByParameter, singleTargetReferences, - SOURCE_PARAM_EXTRACTOR + SourceReference::getParameter ); - for ( Map.Entry> entry : mappingsKeyedByParameter.entrySet() ) { + for ( Map.Entry> entry : mappingsKeyedByParameter.entrySet() ) { entry.getValue().addAll( appliesToAll ); } - List notProcessAppliesToAll = - mappingsKeyedByParameter.isEmpty() ? appliesToAll : new ArrayList<>(); + Set notProcessAppliesToAll = + mappingsKeyedByParameter.isEmpty() ? appliesToAll : new LinkedHashSet<>(); return new GroupedBySourceParameters( mappingsKeyedByParameter, notProcessAppliesToAll ); } @@ -521,31 +522,29 @@ private GroupedBySourceParameters groupBySourceParameter(List mappings, * * * - * @param entryByParam the entry of a {@link Parameter} and it's associated {@link Mapping}(s) that need to - * be used for grouping on popped source references + * @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, - List singleTargetReferences) { - List mappings = entryByParam.getValue(); - List nonNested = new ArrayList<>(); - List appliesToAll = new ArrayList<>(); - List sourceParameterMappings = new ArrayList<>(); + 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 + Map> mappingsKeyedByProperty = new LinkedHashMap<>(); - for ( Mapping mapping : mappings ) { - - Mapping newMapping = mapping.popSourceReference(); + for ( MappingReference mapping : mappings ) { + MappingReference newMapping = mapping.popSourceReference(); if ( newMapping != null ) { // group properties on current name. PropertyEntry property = first( mapping.getSourceReference().getPropertyEntries() ); - if ( !mappingsKeyedByProperty.containsKey( property ) ) { - mappingsKeyedByProperty.put( property, new ArrayList<>() ); - } - mappingsKeyedByProperty.get( property ).add( newMapping ); + 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 ) { @@ -561,7 +560,7 @@ else if ( mapping.getSourceReference() == null ) { // applied to everything. boolean hasNoMappings = mappingsKeyedByProperty.isEmpty() && nonNested.isEmpty(); Parameter sourceParameter = entryByParam.getKey(); - List singleTargetReferencesToUse = + Set singleTargetReferencesToUse = extractSingleTargetReferencesToUseAndPopulateSourceParameterMappings( singleTargetReferences, sourceParameterMappings, @@ -572,14 +571,15 @@ else if ( mapping.getSourceReference() == null ) { populateWithSingleTargetReferences( mappingsKeyedByProperty, singleTargetReferencesToUse, - PROPERTY_EXTRACTOR + sourceReference -> sourceReference.getPropertyEntries().isEmpty() ? null : + first( sourceReference.getPropertyEntries() ) ); - for ( Map.Entry> entry : mappingsKeyedByProperty.entrySet() ) { + for ( Map.Entry> entry : mappingsKeyedByProperty.entrySet() ) { entry.getValue().addAll( appliesToAll ); } - List notProcessedAppliesToAll = new ArrayList<>(); + 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() ) { @@ -612,15 +612,15 @@ else if ( mappingsKeyedByProperty.isEmpty() && nonNested.isEmpty() ) { * @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 list with valid single target references + * @return a set with valid single target references */ - private List extractSingleTargetReferencesToUseAndPopulateSourceParameterMappings( - List singleTargetReferences, List sourceParameterMappings, boolean hasNoMappings, - Parameter sourceParameter) { - List singleTargetReferencesToUse = null; + private Set extractSingleTargetReferencesToUseAndPopulateSourceParameterMappings( + Set singleTargetReferences, Set sourceParameterMappings, + boolean hasNoMappings, Parameter sourceParameter) { + Set singleTargetReferencesToUse = null; if ( singleTargetReferences != null ) { - singleTargetReferencesToUse = new ArrayList<>( singleTargetReferences.size() ); - for ( Mapping mapping : singleTargetReferences ) { + 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 @@ -642,32 +642,57 @@ private List extractSingleTargetReferencesToUseAndPopulateSourceParamet return singleTargetReferencesToUse; } - private Map> groupByTargetName(List mappingList) { - Map> result = new LinkedHashMap<>(); - for ( Mapping mapping : mappingList ) { - if ( !result.containsKey( mapping.getTargetName() ) ) { - result.put( mapping.getTargetName(), new ArrayList<>() ); + 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, "." ) + ); } - result.get( mapping.getTargetName() ).add( mapping ); + + errorOccurred = true; + return null; } - return result; - } - private PropertyMapping createPropertyMappingForNestedTarget(MappingOptions mappingOptions, - PropertyEntry targetProperty, SourceReference sourceReference, boolean forceUpdateMethod) { - PropertyMapping propertyMapping = new PropertyMapping.PropertyMappingBuilder() + return new PropertyMapping.PropertyMappingBuilder() .mappingContext( mappingContext ) .sourceMethod( method ) - .targetProperty( targetProperty ) - .targetPropertyName( targetProperty.getName() ) + .target( targetPropertyName, targetReadAccessor, targetWriteAccessor ) .sourceReference( sourceReference ) .existingVariableNames( existingVariableNames ) - .dependsOn( mappingOptions.collectNestedDependsOn() ) - .forgeMethodWithMappingOptions( mappingOptions ) + .dependsOn( mappingReferences.collectNestedDependsOn() ) + .forgeMethodWithMappingReferences( mappingReferences ) .forceUpdateMethod( forceUpdateMethod ) .forgedNamedBased( false ) + .options( method.getOptions().getBeanMapping() ) .build(); - return propertyMapping; } /** @@ -679,16 +704,16 @@ private PropertyMapping createPropertyMappingForNestedTarget(MappingOptions mapp * @param singleTargetReferences to use * @param keyExtractor to be used to extract a key */ - private void populateWithSingleTargetReferences(Map> map, - List singleTargetReferences, Extractor keyExtractor) { + 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 ( Mapping mapping : singleTargetReferences ) { + for ( MappingReference mapping : singleTargetReferences ) { if ( mapping.getSourceReference() != null && mapping.getSourceReference().isValid() ) { K key = keyExtractor.apply( mapping.getSourceReference() ); - if ( key != null && !map.containsKey( key ) ) { - map.put( key, new ArrayList<>() ); + if ( key != null ) { + map.computeIfAbsent( key, keyValue -> new LinkedHashSet<>() ); } } } @@ -697,11 +722,11 @@ private void populateWithSingleTargetReferences(Map> map, } private static class GroupedBySourceParameters { - private final Map> groupedBySourceParameter; - private final List notProcessedAppliesToAll; + private final Map> groupedBySourceParameter; + private final Set notProcessedAppliesToAll; - private GroupedBySourceParameters(Map> groupedBySourceParameter, - List notProcessedAppliesToAll) { + private GroupedBySourceParameters(Map> groupedBySourceParameter, + Set notProcessedAppliesToAll) { this.groupedBySourceParameter = groupedBySourceParameter; this.notProcessedAppliesToAll = notProcessedAppliesToAll; } @@ -712,20 +737,24 @@ private GroupedBySourceParameters(Map> groupedBySourceP * references (target references that were not nested). */ private static class GroupedTargetReferences { - private final Map> poppedTargetReferences; - private final Map> singleTargetReferences; - private final boolean errorOccurred; + private final Map> poppedTargetReferences; + private final Map> singleTargetReferences; - private GroupedTargetReferences(Map> poppedTargetReferences, - Map> singleTargetReferences, boolean errorOccurred) { + private GroupedTargetReferences(Map> poppedTargetReferences, + Map> singleTargetReferences) { this.poppedTargetReferences = poppedTargetReferences; this.singleTargetReferences = singleTargetReferences; - this.errorOccurred = errorOccurred; + } + + @Override + public String toString() { + return "GroupedTargetReferences{" + "poppedTargetReferences=" + poppedTargetReferences + + ", singleTargetReferences=" + singleTargetReferences + "}"; } } /** - * This class is used to group Source references in respected to the nestings that they have. + * 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. @@ -759,19 +788,26 @@ private GroupedTargetReferences(Map> poppedTargetRe */ private static class GroupedSourceReferences { - private final Map> groupedBySourceReferences; - private final List nonNested; - private final List notProcessedAppliesToAll; - private final List sourceParameterMappings; + private final Map> groupedBySourceReferences; + private final Set nonNested; + private final Set notProcessedAppliesToAll; + private final Set sourceParameterMappings; - private GroupedSourceReferences(Map> groupedBySourceReferences, - List nonNested, List notProcessedAppliesToAll, - List 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/NormalTypeMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java index 4687a626d1..89ee1baab2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NormalTypeMappingMethod.java @@ -21,17 +21,19 @@ */ public abstract class NormalTypeMappingMethod extends MappingMethod { private final MethodReference factoryMethod; - private final boolean overridden; private final boolean mapNullToDefault; - NormalTypeMappingMethod(Method method, Collection existingVariableNames, MethodReference factoryMethod, + 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.overridden = method.overridesMethod(); this.mapNullToDefault = mapNullToDefault; + this.annotations = annotations; } @Override @@ -42,6 +44,12 @@ public Set getImportTypes() { types.addAll( getReturnType().getImplementationType().getImportTypes() ); } } + else if ( factoryMethod != null ) { + types.addAll( factoryMethod.getImportTypes() ); + } + for ( Annotation annotation : annotations ) { + types.addAll( annotation.getImportTypes() ); + } return types; } @@ -49,14 +57,14 @@ public boolean isMapNullToDefault() { return mapNullToDefault; } - public boolean isOverridden() { - return overridden; - } - public MethodReference getFactoryMethod() { return this.factoryMethod; } + public List getAnnotations() { + return annotations; + } + @Override public int hashCode() { final int prime = 31; 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 index 17d5135b00..8b526b9177 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -7,6 +7,7 @@ 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; @@ -19,13 +20,13 @@ 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.SelectionCriteria; +import org.mapstruct.ap.internal.model.source.selector.SelectionContext; 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 object factory method. * * @author Sjaak Derksen */ @@ -38,45 +39,66 @@ private ObjectFactoryMethodResolver() { * returns a no arg factory method * * @param method target mapping method - * @param targetType return type to match * @param selectionParameters parameters used in the selection process - * @param ctx + * @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 targetType, + Type alternativeTarget, SelectionParameters selectionParameters, MappingBuilderContext ctx) { - MethodSelectors selectors = - new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); - List> matchingFactoryMethods = - selectors.getMatchingMethods( - method, - getAllAvailableMethods( method, ctx.getSourceModel() ), - java.util.Collections. emptyList(), - targetType.getEffectiveType(), - SelectionCriteria.forFactoryMethods( selectionParameters ) ); + List> matchingFactoryMethods = getMatchingFactoryMethods( + method, + alternativeTarget, + selectionParameters, + ctx + ); if (matchingFactoryMethods.isEmpty()) { - return findBuilderFactoryMethod( targetType ); + return null; } if ( matchingFactoryMethods.size() > 1 ) { ctx.getMessager().printMessage( method.getExecutable(), - Message.GENERAL_AMBIGIOUS_FACTORY_METHOD, - targetType.getEffectiveType(), - Strings.join( matchingFactoryMethods, ", " ) ); + 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() ); @@ -98,8 +120,25 @@ java.util.Collections. emptyList(), } } - private static MethodReference findBuilderFactoryMethod(Type targetType) { - BuilderType builder = targetType.getBuilderType(); + 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 ) { + return getBuilderFactoryMethod( method.getReturnType(), builder ); + } + + public static MethodReference getBuilderFactoryMethod(Type typeToBuild, BuilderType builder ) { if ( builder == null ) { return null; } @@ -110,7 +149,7 @@ private static MethodReference findBuilderFactoryMethod(Type targetType) { return null; } - if ( !builder.getBuildingType().isAssignableTo( targetType ) ) { + if ( !builder.getBuildingType().isAssignableTo( typeToBuild ) ) { //TODO print error message return null; } 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..2de61089ca --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.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; + +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; + +/** + * 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() ) { + 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 2f892e6b5e..c120f035b2 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 @@ -9,11 +9,16 @@ 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.ExecutableElement; -import javax.lang.model.type.DeclaredType; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +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.EnumConstantWrapper; @@ -21,39 +26,43 @@ import org.mapstruct.ap.internal.model.assignment.SetterWrapper; 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.BeanMapping; -import org.mapstruct.ap.internal.model.source.ForgedMethod; -import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; +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.ParameterProvidedMethods; -import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.SelectionParameters; -import org.mapstruct.ap.internal.model.source.SourceReference; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; -import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; -import org.mapstruct.ap.internal.util.AccessorNamingUtils; -import org.mapstruct.ap.internal.util.Executables; -import org.mapstruct.ap.internal.util.MapperConfiguration; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.NativeTypes; import org.mapstruct.ap.internal.util.Strings; -import org.mapstruct.ap.internal.util.ValueProvider; 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; -import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT; -import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_NULL; -import static org.mapstruct.ap.internal.util.Collections.first; -import static org.mapstruct.ap.internal.util.Collections.last; /** * Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to @@ -65,51 +74,28 @@ public class PropertyMapping extends ModelElement { private final String name; + private final String sourcePropertyName; private final String sourceBeanName; private final String targetWriteAccessorName; - private final ValueProvider targetReadAccessorProvider; + private final ReadAccessor targetReadAccessorProvider; private final Type targetType; private final Assignment assignment; - private final List dependsOn; + private final Set dependsOn; private final Assignment defaultValueAssignment; - - public enum TargetWriteAccessorType { - FIELD, - GETTER, - SETTER, - ADDER; - - public static TargetWriteAccessorType of(AccessorNamingUtils accessorNaming, Accessor accessor) { - if ( accessorNaming.isSetterMethod( accessor ) ) { - return TargetWriteAccessorType.SETTER; - } - else if ( accessorNaming.isAdderMethod( accessor ) ) { - return TargetWriteAccessorType.ADDER; - } - else if ( accessorNaming.isGetterMethod( accessor ) ) { - return TargetWriteAccessorType.GETTER; - } - else { - return TargetWriteAccessorType.FIELD; - } - } - - public static boolean isFieldAssignment(TargetWriteAccessorType accessorType) { - return accessorType == FIELD; - } - } + private final boolean constructorMapping; @SuppressWarnings("unchecked") private static class MappingBuilderBase> extends AbstractBaseBuilder { protected Accessor targetWriteAccessor; - protected TargetWriteAccessorType targetWriteAccessorType; + protected AccessorType targetWriteAccessorType; protected Type targetType; - protected Accessor targetReadAccessor; + protected BuilderType targetBuilderType; + protected ReadAccessor targetReadAccessor; protected String targetPropertyName; protected String sourcePropertyName; - protected List dependsOn; + protected Set dependsOn; protected Set existingVariableNames; protected AnnotationMirror positionHint; @@ -121,24 +107,14 @@ public T sourceMethod(Method sourceMethod) { return super.method( sourceMethod ); } - public T targetProperty(PropertyEntry targetProp) { - this.targetReadAccessor = targetProp.getReadAccessor(); - this.targetWriteAccessor = targetProp.getWriteAccessor(); - this.targetType = targetProp.getType(); - this.targetWriteAccessorType = TargetWriteAccessorType.of( ctx.getAccessorNaming(), targetWriteAccessor ); - return (T) this; - } - - public T targetReadAccessor(Accessor targetReadAccessor) { + public T target(String targetPropertyName, ReadAccessor targetReadAccessor, Accessor targetWriteAccessor) { + this.targetPropertyName = targetPropertyName; this.targetReadAccessor = targetReadAccessor; - return (T) this; - } - - public T targetWriteAccessor(Accessor targetWriteAccessor) { this.targetWriteAccessor = targetWriteAccessor; - this.targetWriteAccessorType = TargetWriteAccessorType.of( ctx.getAccessorNaming(), targetWriteAccessor ); - this.targetType = determineTargetType(); - + 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; } @@ -147,39 +123,12 @@ T mirror(AnnotationMirror mirror) { return (T) this; } - private Type determineTargetType() { - // This is a bean mapping method, so we know the result is a declared type - Type mappingType = method.getResultType(); - if ( !method.isUpdateMethod() ) { - mappingType = mappingType.getEffectiveType(); - } - DeclaredType resultType = (DeclaredType) mappingType.getTypeMirror(); - - switch ( targetWriteAccessorType ) { - case ADDER: - case SETTER: - return ctx.getTypeFactory() - .getSingleParameter( resultType, targetWriteAccessor ) - .getType(); - case GETTER: - case FIELD: - default: - return ctx.getTypeFactory() - .getReturnType( resultType, targetWriteAccessor ); - } - } - - public T targetPropertyName(String targetPropertyName) { - this.targetPropertyName = targetPropertyName; - return (T) this; - } - 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; } @@ -190,7 +139,7 @@ public T existingVariableNames(Set existingVariableNames) { } protected boolean isFieldAssignment() { - return targetWriteAccessorType == TargetWriteAccessorType.FIELD; + return targetWriteAccessorType.isFieldAssignment(); } } @@ -199,16 +148,17 @@ public static class PropertyMappingBuilder extends MappingBuilderBase do a null check + return true; + } + + 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; + } + + return false; + } + + private boolean hasDefaultValueOrDefaultExpression() { + return defaultValue != null || defaultJavaExpression != null; } private Assignment assignToPlainViaAdder( Assignment rightHandSide) { @@ -509,7 +538,7 @@ private Assignment assignToPlainViaAdder( Assignment rightHandSide) { Assignment result = rightHandSide; String adderIteratorName = sourcePropertyName == null ? targetPropertyName : sourcePropertyName; - if ( result.getSourceType().isCollectionType() ) { + if ( result.getSourceType().isIterableType() ) { result = new AdderWrapper( result, method.getThrownTypes(), isFieldAssignment(), adderIteratorName ); } else if ( result.getSourceType().isStreamType() ) { @@ -524,13 +553,34 @@ else if ( result.getSourceType().isStreamType() ) { isFieldAssignment(), true, nvpms == SET_TO_NULL && !targetType.isPrimitive(), - nvpms == SET_TO_DEFAULT + nvpms == SET_TO_DEFAULT, + hasTwoOrMoreSettersWithName(), + targetType ); } return result; } - private Assignment assignToCollection(Type targetType, TargetWriteAccessorType targetAccessorType, + 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 Assignment assignToCollection(Type targetType, AccessorType targetAccessorType, Assignment rhs) { return new CollectionAssignmentBuilder() .mappingBuilderContext( ctx ) @@ -541,7 +591,7 @@ private Assignment assignToCollection(Type targetType, TargetWriteAccessorType t .targetAccessorType( targetAccessorType ) .rightHandSide( rightHandSide ) .assignment( rhs ) - .nullValueCheckStrategy( nvcs ) + .nullValueCheckStrategy( hasDefaultValueOrDefaultExpression() ? ALWAYS : nvcs ) .nullValuePropertyMappingStrategy( nvpms ) .build(); } @@ -549,7 +599,8 @@ private Assignment assignToCollection(Type targetType, TargetWriteAccessorType t private Assignment assignToArray(Type targetType, Assignment rightHandSide) { Type arrayType = ctx.getTypeFactory().getType( Arrays.class ); - Assignment assignment = new ArrayCopyWrapper( + //TODO init default value + return new ArrayCopyWrapper( rightHandSide, targetPropertyName, arrayType, @@ -557,36 +608,46 @@ private Assignment assignToArray(Type targetType, Assignment rightHandSide) { isFieldAssignment(), nvpms == SET_TO_NULL && !targetType.isPrimitive(), nvpms == SET_TO_DEFAULT ); - return assignment; } private SourceRHS getSourceRHS( SourceReference sourceReference ) { Parameter sourceParam = sourceReference.getParameter(); - List propertyEntries = sourceReference.getPropertyEntries(); + PropertyEntry propertyEntry = sourceReference.getDeepestProperty(); // parameter reference - if ( propertyEntries.isEmpty() ) { - return new SourceRHS( sourceParam.getName(), - sourceParam.getType(), - existingVariableNames, - sourceReference.toString() + 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 ); - String sourceRef = sourceParam.getName() + "." + ValueProvider.of( propertyEntry.getReadAccessor() ); - return new SourceRHS( sourceParam.getName(), - sourceRef, - getSourcePresenceCheckerRef( sourceReference ), - propertyEntry.getType(), - existingVariableNames, - sourceReference.toString() + 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 { - Type sourceType = last( propertyEntries ).getType(); + 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 @@ -594,20 +655,11 @@ else if ( propertyEntries.size() == 1 ) { sourceType = ctx.getTypeFactory().getWrappedType( sourceType ); } - // copy mapper configuration from the source method, its the same mapper - MapperConfiguration config = method.getMapperConfiguration(); - // forge a method from the parameter type to the last entry type. String forgedName = Strings.joinAndCamelize( sourceReference.getElementNames() ); forgedName = Strings.getSafeVariableName( forgedName, ctx.getReservedNames() ); - ForgedMethod methodRef = new ForgedMethod( - forgedName, - sourceReference.getParameter().getType(), - sourceType, - config, - method.getExecutable(), - Collections. emptyList(), - ParameterProvidedMethods.empty() ); + Type sourceParameterType = sourceReference.getParameter().getType(); + ForgedMethod methodRef = forParameterMapping( forgedName, sourceParameterType, sourceType, method ); NestedPropertyMappingMethod.Builder builder = new NestedPropertyMappingMethod.Builder(); NestedPropertyMappingMethod nestedPropertyMapping = builder @@ -626,14 +678,18 @@ Collections. emptyList(), String sourceRef = forgedName + "( " + sourceParam.getName() + " )"; SourceRHS sourceRhs = new SourceRHS( sourceParam.getName(), sourceRef, - getSourcePresenceCheckerRef( sourceReference ), + null, sourceType, existingVariableNames, sourceReference.toString() ); + sourceRhs.setSourcePresenceCheckerReference( getSourcePresenceCheckerRef( + sourceReference, + sourceRhs + ) ); // create a local variable to which forged method can be assigned. - String desiredName = last( sourceReference.getPropertyEntries() ).getName(); + String desiredName = propertyEntry.getName(); sourceRhs.setSourceLocalVarName( sourceRhs.createUniqueVarName( desiredName ) ); return sourceRhs; @@ -641,104 +697,132 @@ Collections. emptyList(), } } - private String getSourcePresenceCheckerRef( SourceReference sourceReference ) { - String sourcePresenceChecker = null; + private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReference, + SourceRHS sourceRHS) { + + if ( conditionJavaExpression != null ) { + return new JavaExpressionPresenceCheck( conditionJavaExpression ); + } + + SelectionParameters selectionParameters = this.selectionParameters != null ? + this.selectionParameters.withSourceRHS( sourceRHS ) : + SelectionParameters.forSourceRHS( sourceRHS ); + PresenceCheck presenceCheck = PresenceCheckMethodResolver.getPresenceCheck( + method, + selectionParameters, + ctx + ); + if ( presenceCheck != null ) { + return presenceCheck; + } + + 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 = first( sourceReference.getPropertyEntries() ); + PropertyEntry propertyEntry = sourceReference.getShallowestProperty(); if ( propertyEntry.getPresenceChecker() != null ) { - sourcePresenceChecker = sourceParam.getName() - + "." + propertyEntry.getPresenceChecker().getSimpleName() + "()"; + List presenceChecks = new ArrayList<>(); + presenceChecks.add( new SuffixPresenceCheck( + sourceParam.getName(), + propertyEntry.getPresenceChecker().getPresenceCheckSuffix() + ) ); String variableName = sourceParam.getName() + "." - + propertyEntry.getReadAccessor().getSimpleName() + "()"; + + 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) { - sourcePresenceChecker += " && " + variableName + " != null && " - + variableName + "." + entry.getPresenceChecker().getSimpleName() + "()"; + 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 Assignment forgeStreamMapping(Type sourceType, Type targetType, SourceRHS source, - ExecutableElement element) { + private Assignment forgeStreamMapping(Type sourceType, Type targetType, SourceRHS source) { StreamMappingMethod.Builder builder = new StreamMappingMethod.Builder(); - return forgeWithElementMapping( sourceType, targetType, source, element, builder ); + return forgeWithElementMapping( sourceType, targetType, source, builder ); } - private Assignment forgeIterableMapping(Type sourceType, Type targetType, SourceRHS source, - ExecutableElement element) { + private Assignment forgeIterableMapping(Type sourceType, Type targetType, SourceRHS source) { IterableMappingMethod.Builder builder = new IterableMappingMethod.Builder(); - return forgeWithElementMapping( sourceType, targetType, source, element, builder ); + return forgeWithElementMapping( sourceType, targetType, source, builder ); } private Assignment forgeWithElementMapping(Type sourceType, Type targetType, SourceRHS source, - ExecutableElement element, ContainerMappingMethodBuilder builder) { + ContainerMappingMethodBuilder builder) { targetType = targetType.withoutBounds(); - ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, element, "[]" ); + ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "[]" ); - ContainerMappingMethod iterableMappingMethod = builder + Supplier mappingMethodCreator = () -> builder .mappingContext( ctx ) .method( methodRef ) .selectionParameters( selectionParameters ) .callingContextTargetPropertyName( targetPropertyName ) + .positionHint( positionHint ) .build(); - return createForgedAssignment( source, methodRef, iterableMappingMethod ); + return getOrCreateForgedAssignment( source, methodRef, mappingMethodCreator ); } - private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source, - ExecutableElement element, String suffix) { + private ForgedMethod prepareForgedMethod(Type sourceType, Type targetType, SourceRHS source, String suffix) { String name = getName( sourceType, targetType ); name = Strings.getSafeVariableName( name, ctx.getReservedNames() ); // copy mapper configuration from the source method, its the same mapper - MapperConfiguration config = method.getMapperConfiguration(); - return new ForgedMethod( - name, - sourceType, - targetType, - config, - element, - method.getContextParameters(), - method.getContextProvidedMethods(), - getForgedMethodHistory( source, suffix ), - null, - forgedNamedBased - ); + ForgedMethodHistory forgedMethodHistory = getForgedMethodHistory( source, suffix ); + return forElementMapping( name, sourceType, targetType, method, forgedMethodHistory, forgedNamedBased ); } - private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS source, - ExecutableElement element) { + private Assignment forgeMapMapping(Type sourceType, Type targetType, SourceRHS source) { targetType = targetType.withoutBounds(); - ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, element, "{}" ); + ForgedMethod methodRef = prepareForgedMethod( sourceType, targetType, source, "{}" ); MapMappingMethod.Builder builder = new MapMappingMethod.Builder(); - MapMappingMethod mapMappingMethod = builder + Supplier mapMappingMethodCreator = () -> builder .mappingContext( ctx ) .method( methodRef ) .build(); - return createForgedAssignment( source, methodRef, mapMappingMethod ); + return getOrCreateForgedAssignment( source, methodRef, mapMappingMethodCreator ); } private Assignment forgeMapping(SourceRHS sourceRHS) { Type sourceType; - if ( targetWriteAccessorType == TargetWriteAccessorType.ADDER ) { + if ( targetWriteAccessorType == AccessorType.ADDER ) { sourceType = sourceRHS.getSourceTypeForMatching(); } else { @@ -748,6 +832,10 @@ private Assignment forgeMapping(SourceRHS sourceRHS) { return null; } + return forgeMapping( sourceType, targetType, sourceRHS ); + } + + private Assignment forgeMapping(Type sourceType, Type targetType, SourceRHS sourceRHS) { //Fail fast. If we could not find the method by now, no need to try if ( sourceType.isPrimitive() || targetType.isPrimitive() ) { @@ -764,23 +852,20 @@ private Assignment forgeMapping(SourceRHS sourceRHS) { // 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 != TargetWriteAccessorType.ADDER) { + && targetWriteAccessorType != AccessorType.ADDER) { parameters.add( Parameter.forForgedMappingTarget( targetType ) ); returnType = ctx.getTypeFactory().createVoidType(); } else { returnType = targetType; } - ForgedMethod forgedMethod = new ForgedMethod( - name, + ForgedMethod forgedMethod = forPropertyMapping( name, sourceType, returnType, - method.getMapperConfiguration(), - method.getExecutable(), parameters, - method.getContextProvidedMethods(), + method, getForgedMethodHistory( sourceRHS ), - forgeMethodWithMappingOptions, + forgeMethodWithMappingReferences, forgedNamedBased ); return createForgedAssignment( sourceRHS, forgedMethod ); @@ -836,6 +921,7 @@ public static class ConstantMappingBuilder extends MappingBuilderBase null ); } else { @@ -899,8 +993,8 @@ public PropertyMapping build() { if ( assignment != null ) { - if ( ctx.getAccessorNaming().isSetterMethod( targetWriteAccessor ) || - Executables.isFieldAccessor( targetWriteAccessor ) ) { + if ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER || + targetWriteAccessor.getAccessorType().isFieldAssignment() ) { // target accessor is setter, so decorate assignment as setter if ( assignment.isCallingUpdateMethod() ) { @@ -924,6 +1018,7 @@ public PropertyMapping build() { targetType, false, false, + false, false ); } else { @@ -945,9 +1040,8 @@ else if ( errorMessageDetails == null ) { method.getExecutable(), positionHint, Message.CONSTANTMAPPING_MAPPING_NOT_FOUND, - sourceType, constantExpression, - targetType, + targetType.describe(), targetPropertyName ); } @@ -956,9 +1050,8 @@ else if ( errorMessageDetails == null ) { method.getExecutable(), positionHint, Message.CONSTANTMAPPING_MAPPING_NOT_FOUND_WITH_DETAILS, - sourceType, constantExpression, - targetType, + targetType.describe(), targetPropertyName, errorMessageDetails ); @@ -966,12 +1059,13 @@ else if ( errorMessageDetails == null ) { return new PropertyMapping( targetPropertyName, - targetWriteAccessor.getSimpleName().toString(), - ValueProvider.of( targetReadAccessor ), + targetWriteAccessor.getSimpleName(), + targetReadAccessor, targetType, assignment, dependsOn, - null + null, + targetWriteAccessorType == AccessorType.PARAMETER ); } @@ -990,7 +1084,7 @@ private Assignment getEnumAssignment() { positionHint, Message.CONSTANTMAPPING_NON_EXISTING_CONSTANT, constantExpression, - targetType, + targetType.describe(), targetPropertyName ); } @@ -1015,8 +1109,8 @@ public JavaExpressionMappingBuilder javaExpression(String javaExpression) { public PropertyMapping build() { Assignment assignment = new SourceRHS( javaExpression, null, existingVariableNames, "" ); - if ( ctx.getAccessorNaming().isSetterMethod( targetWriteAccessor ) || - Executables.isFieldAccessor( targetWriteAccessor ) ) { + if ( targetWriteAccessor.getAccessorType() == AccessorType.SETTER || + targetWriteAccessor.getAccessorType().isFieldAssignment() ) { // setter, so wrap in setter assignment = new SetterWrapper( assignment, method.getThrownTypes(), isFieldAssignment() ); } @@ -1031,12 +1125,13 @@ public PropertyMapping build() { return new PropertyMapping( targetPropertyName, - targetWriteAccessor.getSimpleName().toString(), - ValueProvider.of( targetReadAccessor ), + targetWriteAccessor.getSimpleName(), + targetReadAccessor, targetType, assignment, dependsOn, - null + null, + targetWriteAccessorType == AccessorType.PARAMETER ); } @@ -1044,18 +1139,20 @@ public PropertyMapping build() { // Constructor for creating mappings of constant expressions. private PropertyMapping(String name, String targetWriteAccessorName, - ValueProvider targetReadAccessorProvider, - Type targetType, Assignment propertyAssignment, - List dependsOn, Assignment defaultValueAssignment ) { - this( name, null, targetWriteAccessorName, targetReadAccessorProvider, - targetType, propertyAssignment, dependsOn, defaultValueAssignment + 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, - ValueProvider targetReadAccessorProvider, Type targetType, + private PropertyMapping(String sourcePropertyName, String name, String sourceBeanName, + String targetWriteAccessorName, ReadAccessor targetReadAccessorProvider, Type targetType, Assignment assignment, - List dependsOn, Assignment defaultValueAssignment) { + Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + this.sourcePropertyName = sourcePropertyName; this.name = name; this.sourceBeanName = sourceBeanName; this.targetWriteAccessorName = targetWriteAccessorName; @@ -1063,8 +1160,9 @@ private PropertyMapping(String name, String sourceBeanName, String targetWriteAc 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; } /** @@ -1074,6 +1172,10 @@ public String getName() { return name; } + public String getSourcePropertyName() { + return sourcePropertyName; + } + public String getSourceBeanName() { return sourceBeanName; } @@ -1083,7 +1185,7 @@ public String getTargetWriteAccessorName() { } public String getTargetReadAccessorName() { - return targetReadAccessorProvider == null ? null : targetReadAccessorProvider.getValue(); + return targetReadAccessorProvider == null ? null : targetReadAccessorProvider.getReadValueSource(); } public Type getTargetType() { @@ -1098,8 +1200,11 @@ public Assignment getDefaultValueAssignment() { return defaultValueAssignment; } + public boolean isConstructorMapping() { + return constructorMapping; + } + @Override - @SuppressWarnings("unchecked") public Set getImportTypes() { if ( defaultValueAssignment == null ) { return assignment.getImportTypes(); @@ -1111,7 +1216,7 @@ public Set getImportTypes() { ); } - public List getDependsOn() { + public Set getDependsOn() { return dependsOn; } @@ -1135,13 +1240,13 @@ public boolean equals(Object obj) { return false; } final PropertyMapping other = (PropertyMapping) obj; - if ( (this.name == null) ? (other.name != null) : !this.name.equals( other.name ) ) { + if ( !Objects.equals( name, other.name ) ) { return false; } - if ( this.targetType != other.targetType && (this.targetType == null || - !this.targetType.equals( other.targetType )) ) { + if ( !Objects.equals( targetType, other.targetType ) ) { return false; } + return true; } 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 727518dad8..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 @@ -12,6 +12,8 @@ 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 index 552040d8ce..57ee9ccdf4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/StreamMappingMethod.java @@ -5,6 +5,12 @@ */ package org.mapstruct.ap.internal.model; +import org.mapstruct.ap.internal.model.assignment.Java8FunctionWrapper; +import org.mapstruct.ap.internal.model.common.Assignment; +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 java.util.Collection; import java.util.HashSet; import java.util.List; @@ -13,12 +19,6 @@ 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.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; /** @@ -63,9 +63,9 @@ protected StreamMappingMethod instantiateMappingMethod(Method method, Collection sourceParameterType.isIterableType() ) { helperImports.add( ctx.getTypeFactory().getType( StreamSupport.class ) ); } - return new StreamMappingMethod( method, + getMethodAnnotations(), existingVariables, assignment, factoryMethod, @@ -78,14 +78,16 @@ protected StreamMappingMethod instantiateMappingMethod(Method method, Collection ); } } - - private StreamMappingMethod(Method method, Collection existingVariables, Assignment parameterAssignment, + //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) { super( method, + annotations, existingVariables, parameterAssignment, factoryMethod, @@ -95,6 +97,7 @@ private StreamMappingMethod(Method method, Collection existingVariables, afterMappingReferences, selectionParameters ); + //CHECKSTYLE:ON this.helperImports = helperImports; } 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 index 14a13ca735..936a9a51ba 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingConstructorFragment.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingConstructorFragment.java @@ -6,11 +6,12 @@ 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; -import org.mapstruct.ap.internal.model.source.builtin.BuiltInConstructorFragment; /** * A mapper instance field, initialized as null @@ -19,13 +20,15 @@ */ public class SupportingConstructorFragment extends ModelElement { + private final String variableName; private final String templateName; private final SupportingMappingMethod definingMethod; public SupportingConstructorFragment(SupportingMappingMethod definingMethod, - BuiltInConstructorFragment constructorFragment) { + ConstructorFragment constructorFragment, String variableName) { this.templateName = getTemplateNameForClass( constructorFragment.getClass() ); this.definingMethod = definingMethod; + this.variableName = variableName; } @Override @@ -42,10 +45,15 @@ 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; } @@ -62,12 +70,11 @@ public boolean equals(Object obj) { return false; } SupportingConstructorFragment other = (SupportingConstructorFragment) obj; - if ( templateName == null ) { - if ( other.templateName != null ) { - return false; - } + + if ( !Objects.equals( variableName, other.variableName ) ) { + return false; } - else if ( !templateName.equals( other.templateName ) ) { + if ( !Objects.equals( templateName, other.templateName ) ) { return false; } return true; @@ -82,4 +89,17 @@ public static void addAllFragmentsIn(Set supportingMapp } } } + + 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 index e9d0d1e3ca..2cac1ef351 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingField.java @@ -5,9 +5,11 @@ */ package org.mapstruct.ap.internal.model; +import java.util.Map; import java.util.Set; -import org.mapstruct.ap.internal.model.source.builtin.BuiltInFieldReference; +import org.mapstruct.ap.internal.model.common.FieldReference; +import org.mapstruct.ap.internal.util.Strings; /** * supports the @@ -17,11 +19,14 @@ public class SupportingField extends Field { private final String templateName; + private final Map templateParameter; + private final SupportingMappingMethod definingMethod; - public SupportingField(SupportingMappingMethod definingMethod, BuiltInFieldReference fieldReference, String name) { + 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; } @@ -30,47 +35,37 @@ public String getTemplateName() { return templateName; } - public SupportingMappingMethod getDefiningMethod() { - return definingMethod; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ( ( templateName == null ) ? 0 : templateName.hashCode() ); - return result; + public Map getTemplateParameter() { + return templateParameter; } - @Override - public boolean equals(Object obj) { - if ( this == obj ) { - return true; - } - if ( obj == null ) { - return false; - } - if ( getClass() != obj.getClass() ) { - return false; - } - SupportingField other = (SupportingField) obj; - if ( templateName == null ) { - if ( other.templateName != null ) { - return false; - } - } - else if ( !templateName.equals( other.templateName ) ) { - return false; - } - return true; + 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( supportingMappingMethod.getSupportingField() ); + 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 index 8827bc5d35..9428454c1f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/SupportingMappingMethod.java @@ -5,13 +5,13 @@ */ 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.BuiltInFieldReference; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; import org.mapstruct.ap.internal.model.source.builtin.NewDatatypeFactoryConstructorFragment; -import org.mapstruct.ap.internal.util.Strings; /** * A mapping method which is not based on an actual method declared in the original mapper interface but is added as @@ -20,7 +20,7 @@ * 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.source.builtin.FinalField} and + * {@link org.mapstruct.ap.internal.model.common.FinalField} and * {@link NewDatatypeFactoryConstructorFragment} * * @author Gunnar Morling @@ -31,54 +31,25 @@ public class SupportingMappingMethod extends MappingMethod { 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() ); - if ( method.getFieldReference() != null ) { - this.supportingField = getSafeField( method.getFieldReference(), existingFields ); - } - else { - this.supportingField = null; - } - if ( method.getConstructorFragment() != null ) { - this.supportingConstructorFragment = new SupportingConstructorFragment( - this, - method.getConstructorFragment() - ); - } - else { - this.supportingConstructorFragment = null; - } - } - - private Field getSafeField(BuiltInFieldReference ref, Set existingFields) { - Field result = null; - String name = ref.getVariableName(); - for ( Field existingField : existingFields ) { - if ( existingField.getType().equals( ref.getType() ) ) { - // field type already exist, use that one - return existingField; - } - } - for ( Field existingField : existingFields ) { - if ( existingField.getVariableName().equals( ref.getVariableName() ) ) { - // field with name exist, however its a wrong type - name = Strings.getSafeVariableName( name, Field.getFieldNames( existingFields ) ); - } - } - if ( result == null ) { - result = new SupportingField( this, ref, name ); - } - - return result; + 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; } @@ -124,11 +95,15 @@ public SupportingConstructorFragment getSupportingConstructorFragment() { return supportingConstructorFragment; } + public Map getTemplateParameter() { + return templateParameter; + } + @Override public int hashCode() { final int prime = 31; int result = 1; - result = prime * result + ( ( templateName == null ) ? 0 : templateName.hashCode() ); + result = prime * result + ( ( getName() == null ) ? 0 : getName().hashCode() ); return result; } @@ -144,14 +119,11 @@ public boolean equals(Object obj) { return false; } SupportingMappingMethod other = (SupportingMappingMethod) obj; - if ( templateName == null ) { - if ( other.templateName != null ) { - return false; - } - } - else if ( !templateName.equals( other.templateName ) ) { + + 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 3f06ce7e85..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 @@ -11,6 +11,7 @@ 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; /** @@ -79,7 +80,7 @@ public boolean isSourceReferenceParameter() { } @Override - public String getSourcePresenceCheckerReference() { + public PresenceCheck getSourcePresenceCheckerReference() { return assignment.getSourcePresenceCheckerReference(); } 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 index c05d9bdb2b..46af8f0efd 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ValueMappingMethod.java @@ -5,49 +5,60 @@ */ package org.mapstruct.ap.internal.model; -import static org.mapstruct.ap.internal.util.Collections.first; - 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 javax.lang.model.util.Types; +import org.mapstruct.ap.internal.gem.BeanMappingGem; import org.mapstruct.ap.internal.model.common.Parameter; -import org.mapstruct.ap.internal.model.source.ForgedMethod; -import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; +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.ValueMapping; -import org.mapstruct.ap.internal.prism.BeanMappingPrism; -import org.mapstruct.ap.internal.prism.MappingConstantsPrism; +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.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 ValueMapping}s. For now, only enum-to-enum mapping is supported. + * {@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 String defaultTarget; - private final String nullTarget; - private final boolean throwIllegalArgumentException; + private final MappingEntry defaultTarget; + private final MappingEntry nullTarget; + + private final Type unexpectedValueMappingException; + private final boolean overridden; public static class Builder { private Method method; private MappingBuilderContext ctx; - private final List trueValueMappings = new ArrayList<>(); - private ValueMapping defaultTargetValue = null; - private ValueMapping nullTargetValue = null; - private boolean applyNamebasedMappings = true; + private ValueMappings valueMappings; + private EnumMappingOptions enumMapping; + private EnumTransformationStrategyInvoker enumTransformationInvoker; + private boolean enumTransformationIllegalReported = false; public Builder mappingContext(MappingBuilderContext mappingContext) { this.ctx = mappingContext; @@ -59,50 +70,45 @@ public Builder method(Method sourceMethod) { return this; } - public Builder valueMappings(List valueMappings) { - for ( ValueMapping valueMapping : valueMappings ) { - if ( MappingConstantsPrism.ANY_REMAINING.equals( valueMapping.getSource() ) ) { - defaultTargetValue = valueMapping; - } - else if ( MappingConstantsPrism.ANY_UNMAPPED.equals( valueMapping.getSource() ) ) { - defaultTargetValue = valueMapping; - applyNamebasedMappings = false; - } - else if ( MappingConstantsPrism.NULL.equals( valueMapping.getSource() ) ) { - nullTargetValue = valueMapping; - } - else { - trueValueMappings.add( valueMapping ); - } - } + public Builder valueMappings(List valueMappings) { + this.valueMappings = new ValueMappings( valueMappings ); return this; } - public ValueMappingMethod build( ) { + 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<>(); - String nullTarget = null; - String defaultTarget = null; - boolean throwIllegalArgumentException = false; - // for now, we're only dealing with enum mappings, populate relevant parameters based on enum-2-enum - if ( first( method.getSourceParameters() ).getType().isEnumType() && method.getResultType().isEnumType() ) { - mappingEntries.addAll( enumToEnumMapping( method ) ); + Type sourceType = first( method.getSourceParameters() ).getType(); + Type targetType = method.getResultType(); - if ( (nullTargetValue != null) && !MappingConstantsPrism.NULL.equals( nullTargetValue.getTarget() ) ) { - // absense nulltargetvalue reverts to null. Or it could be a deliberate choice to return null - nullTarget = nullTargetValue.getTarget(); - } - if ( defaultTargetValue != null ) { - // If the default target value is NULL then we should map it to null - defaultTarget = MappingConstantsPrism.NULL.equals( defaultTargetValue.getTarget() ) ? null : - defaultTargetValue.getTarget(); - } - else { - throwIllegalArgumentException = true; - } + 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 @@ -112,57 +118,142 @@ public ValueMappingMethod build( ) { 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, mappingEntries, nullTarget, defaultTarget, - throwIllegalArgumentException, beforeMappingMethods, afterMappingMethods ); + return new ValueMappingMethod( method, + annotations, + mappingEntries, + valueMappings.nullValueTarget, + valueMappings.defaultTargetValue, + determineUnexpectedValueMappingException(), + beforeMappingMethods, + afterMappingMethods + ); } - private List enumToEnumMapping(Method method) { + 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() ); + } + } - List mappings = new ArrayList<>(); - List unmappedSourceConstants - = new ArrayList<>( first( method.getSourceParameters() ).getType().getEnumConstants() ); + } + + 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 ) { - if ( !reportErrorIfMappedEnumConstantsDontExist( method ) ) { + 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 valuemappings - for ( ValueMapping valueMapping : trueValueMappings ) { - String target = - MappingConstantsPrism.NULL.equals( valueMapping.getTarget() ) ? null : valueMapping.getTarget(); - mappings.add( new MappingEntry( valueMapping.getSource(), target ) ); + // 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 ( applyNamebasedMappings ) { + 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 ); + } + } - // get all target constants - List targetConstants = method.getReturnType().getEnumConstants(); + TypeElement sourceTypeElement = sourceType.getTypeElement(); for ( String sourceConstant : new ArrayList<>( unmappedSourceConstants ) ) { - if ( targetConstants.contains( sourceConstant ) ) { - mappings.add( new MappingEntry( sourceConstant, sourceConstant ) ); + 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 ( defaultTargetValue == null && !unmappedSourceConstants.isEmpty() ) { + 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().toString() + " " + history.createTargetPropertyName() + "\""; + "\"" + 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.VALUE_MAPPING_UNMAPPED_SOURCES, + Message.VALUEMAPPING_UNMAPPED_SOURCES, sourceErrorMessage, targetErrorMessage, Strings.join( unmappedSourceConstants, ", " ) @@ -173,38 +264,154 @@ private List enumToEnumMapping(Method method) { return mappings; } - private SelectionParameters getSelectionParameters(Method method, Types typeUtils) { - BeanMappingPrism beanMappingPrism = BeanMappingPrism.getInstanceOn( method.getExecutable() ); - if ( beanMappingPrism != null ) { - List qualifiers = beanMappingPrism.qualifiedBy(); - List qualifyingNames = beanMappingPrism.qualifiedByName(); - TypeMirror resultType = beanMappingPrism.resultType(); + 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 reportErrorIfMappedEnumConstantsDontExist(Method method) { - List sourceEnumConstants = first( method.getSourceParameters() ).getType().getEnumConstants(); - List targetEnumConstants = method.getReturnType().getEnumConstants(); + private boolean reportErrorIfMappedSourceEnumConstantsDontExist(Method method, Type sourceType) { + List sourceEnumConstants = sourceType.getEnumConstants(); boolean foundIncorrectMapping = false; - for ( ValueMapping mappedConstant : trueValueMappings ) { + for ( ValueMappingOptions mappedConstant : valueMappings.regularValueMappings ) { - if ( !sourceEnumConstants.contains( mappedConstant.getSource() ) ) { - ctx.getMessager().printMessage( method.getExecutable(), + 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(), - first( method.getSourceParameters() ).getType() + sourceType ); foundIncorrectMapping = true; } - if ( !MappingConstantsPrism.NULL.equals( mappedConstant.getTarget() ) + } + 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(), + ctx.getMessager().printMessage( + method.getExecutable(), mappedConstant.getMirror(), mappedConstant.getTargetAnnotationValue(), Message.VALUEMAPPING_NON_EXISTING_CONSTANT, @@ -215,59 +422,184 @@ private boolean reportErrorIfMappedEnumConstantsDontExist(Method method) { } } - if ( defaultTargetValue != null && !MappingConstantsPrism.NULL.equals( defaultTargetValue.getTarget() ) - && !targetEnumConstants.contains( defaultTargetValue.getTarget() ) ) { - ctx.getMessager().printMessage( method.getExecutable(), - defaultTargetValue.getMirror(), - defaultTargetValue.getTargetAnnotationValue(), + 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, - defaultTargetValue.getTarget(), + valueMappings.defaultTarget.getTarget(), method.getReturnType() ); foundIncorrectMapping = true; } - if ( nullTargetValue != null && MappingConstantsPrism.NULL.equals( nullTargetValue.getTarget() ) - && !targetEnumConstants.contains( nullTargetValue.getTarget() ) ) { + if ( valueMappings.nullTarget != null && NULL.equals( valueMappings.nullTarget.getTarget() ) + && !targetEnumConstants.contains( valueMappings.nullTarget.getTarget() ) ) { ctx.getMessager().printMessage( method.getExecutable(), - nullTargetValue.getMirror(), - nullTargetValue.getTargetAnnotationValue(), + valueMappings.nullTarget.getMirror(), + valueMappings.nullTarget.getTargetAnnotationValue(), Message.VALUEMAPPING_NON_EXISTING_CONSTANT, - nullTargetValue.getTarget(), + 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 !foundIncorrectMapping; + return ctx.getTypeFactory() + .getType( ctx.getEnumMappingStrategy().getUnexpectedValueMappingExceptionType() ); } } - private ValueMappingMethod(Method method, List enumMappings, String nullTarget, String defaultTarget, - boolean throwIllegalArgumentException, List beforeMappingMethods, - List afterMappingMethods) { + 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) { super( method, beforeMappingMethods, afterMappingMethods ); this.valueMappings = enumMappings; - this.nullTarget = nullTarget; - this.defaultTarget = defaultTarget; - this.throwIllegalArgumentException = throwIllegalArgumentException; + 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; + } + + @Override + public Set getImportTypes() { + Set importTypes = super.getImportTypes(); + + if ( unexpectedValueMappingException != null && !unexpectedValueMappingException.isJavaLangType() ) { + if ( 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 String getDefaultTarget() { + public MappingEntry getDefaultTarget() { return defaultTarget; } - public String getNullTarget() { + public MappingEntry getNullTarget() { return nullTarget; } - public boolean isThrowIllegalArgumentException() { - return throwIllegalArgumentException; + public Type getUnexpectedValueMappingException() { + return unexpectedValueMappingException; } public Parameter getSourceParameter() { @@ -278,13 +610,30 @@ 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 ) { + MappingEntry(String source, String target) { this.source = source; - this.target = target; + 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() { 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 71b5f10a9e..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 @@ -39,7 +39,15 @@ public AdderWrapper( Assignment rhs, // localVar is iteratorVariable String desiredName = Nouns.singularize( adderIteratorName ); rhs.setSourceLoopVarName( rhs.createUniqueVarName( desiredName ) ); - adderType = first( getSourceType().determineTypeArguments( Collection.class ) ); + 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 @@ -74,8 +82,7 @@ public boolean isSetExplicitlyToDefault() { @Override public Set getImportTypes() { - Set imported = new HashSet<>(); - imported.addAll( super.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/ArrayCopyWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/ArrayCopyWrapper.java index db2c7be5ac..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 @@ -40,8 +40,7 @@ public ArrayCopyWrapper(Assignment rhs, @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; 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 551d20d72b..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 @@ -10,6 +10,7 @@ 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; /** @@ -57,7 +58,7 @@ public boolean isSourceReferenceParameter() { } @Override - public String getSourcePresenceCheckerReference() { + public PresenceCheck getSourcePresenceCheckerReference() { return decoratedAssignment.getSourcePresenceCheckerReference(); } 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 index a334312007..eaa2fe68ff 100644 --- 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 @@ -12,6 +12,7 @@ import org.mapstruct.ap.internal.model.common.Type; /** + * Decorates the assignment as an {@link Enum} constant access. * * @author Sjaak Derksen */ 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 index d567b2d287..875368b014 100644 --- 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 @@ -5,24 +5,23 @@ */ package org.mapstruct.ap.internal.model.assignment; -import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS; -import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.IGNORE; -import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT; - 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 org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; + +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 an collection or map initialized on the target bean + * 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. * @@ -34,15 +33,15 @@ public class ExistingInstanceSetterWrapperForCollectionsAndMaps extends SetterWrapperForCollectionsAndMapsWithNullCheck { - private final boolean includeElseBranch; - private final boolean mapNullToDefault; + private final NullValuePropertyMappingStrategyGem nvpms; + private final NullValueCheckStrategyGem nvcs; private final Type targetType; public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, List thrownTypesToExclude, Type targetType, - NullValueCheckStrategyPrism nvcs, - NullValuePropertyMappingStrategyPrism nvpms, + NullValueCheckStrategyGem nvcs, + NullValuePropertyMappingStrategyGem nvpms, TypeFactory typeFactory, boolean fieldAssignment) { @@ -53,14 +52,14 @@ public ExistingInstanceSetterWrapperForCollectionsAndMaps(Assignment decoratedAs typeFactory, fieldAssignment ); - this.mapNullToDefault = SET_TO_DEFAULT == nvpms; + this.nvcs = nvcs; + this.nvpms = nvpms; this.targetType = targetType; - this.includeElseBranch = ALWAYS != nvcs && IGNORE != nvpms; } @Override public Set getImportTypes() { - Set imported = new HashSet( super.getImportTypes() ); + Set imported = new HashSet<>( super.getImportTypes() ); if ( isMapNullToDefault() && ( targetType.getImplementationType() != null ) ) { imported.add( targetType.getImplementationType() ); } @@ -68,11 +67,15 @@ public Set getImportTypes() { } public boolean isIncludeElseBranch() { - return includeElseBranch; + return nvcs != ALWAYS && nvpms != IGNORE; } public boolean isMapNullToDefault() { - return mapNullToDefault; + 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 40e195dcd0..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 @@ -9,14 +9,17 @@ 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 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. @@ -26,6 +29,14 @@ * @author Sjaak Derksen */ 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 ); + } /** * @param decoratedAssignment source RHS @@ -36,6 +47,7 @@ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAnd public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, List thrownTypesToExclude, Type targetType, + NullValuePropertyMappingStrategyGem nvpms, boolean fieldAssignment) { super( @@ -44,6 +56,7 @@ public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment, targetType, fieldAssignment ); + this.ignoreMapNull = nvpms == IGNORE; } @Override @@ -54,4 +67,8 @@ public Set getImportTypes() { } return imported; } + + public boolean isIgnoreMapNull() { + return ignoreMapNull; + } } 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/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 3d8de9fd57..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 @@ -6,16 +6,12 @@ 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; -import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; - -import static org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism.ALWAYS; -import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.IGNORE; -import static org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism.SET_TO_DEFAULT; /** * Wraps the assignment in a target setter. @@ -28,19 +24,25 @@ public class SetterWrapper extends AssignmentWrapper { private final boolean includeSourceNullCheck; private final boolean setExplicitlyToNull; private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; + private final Type nullCastType; public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fieldAssignment, boolean includeSourceNullCheck, boolean setExplicitlyToNull, - boolean setExplicitlyToDefault) { + 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 ) { @@ -49,6 +51,8 @@ public SetterWrapper(Assignment rhs, List thrownTypesToExclude, boolean fi this.includeSourceNullCheck = false; this.setExplicitlyToNull = false; this.setExplicitlyToDefault = false; + this.mustCastForNull = false; + this.nullCastType = null; } @Override @@ -65,6 +69,15 @@ 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; } @@ -77,28 +90,7 @@ public boolean isIncludeSourceNullCheck() { return includeSourceNullCheck; } - /** - * Wraps the assignment in a target setter. include a null check when - * - * - Not if source is the parameter iso property, because the null check is than handled by the bean mapping - * - Not when source is primitive, you can't null check a primitive - * - The source property is fed to a conversion somehow before its assigned to the target - * - The user decided to ALLWAYS include a null check - * - * @param rhs the source righthand side - * @param nvcs null value check strategy - * @param nvpms null value property mapping strategy - * @param targetType the target type - * - * @return include a null check - */ - public static boolean doSourceNullCheck(Assignment rhs, NullValueCheckStrategyPrism nvcs, - NullValuePropertyMappingStrategyPrism nvpms, Type targetType) { - return !rhs.isSourceReferenceParameter() - && !rhs.getSourceType().isPrimitive() - && (ALWAYS == nvcs - || SET_TO_DEFAULT == nvpms || IGNORE == nvpms - || rhs.getType().isConverted() - || (rhs.getType().isDirect() && targetType.isPrimitive())); + public boolean isMustCastForNull() { + return mustCastForNull; } } 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 index 5e3f4d63fe..a0c9f799e9 100644 --- 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 @@ -68,7 +68,7 @@ public boolean isDirectAssignment() { } public boolean isEnumSet() { - return "java.util.EnumSet".equals( targetType.getFullyQualifiedName() ); + 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 index 1deff95a2a..21a028c79f 100644 --- 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 @@ -70,8 +70,7 @@ public boolean isSetExplicitlyToDefault() { @Override public Set getImportTypes() { - Set imported = new HashSet<>(); - imported.addAll( super.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/UpdateWrapper.java b/processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java index dede031456..c3d3f9c446 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 @@ -26,6 +26,7 @@ public class UpdateWrapper extends AssignmentWrapper { private final boolean includeSourceNullCheck; private final boolean setExplicitlyToNull; private final boolean setExplicitlyToDefault; + private final boolean mustCastForNull; public UpdateWrapper( Assignment decoratedAssignment, List thrownTypesToExclude, @@ -34,7 +35,8 @@ public UpdateWrapper( Assignment decoratedAssignment, Type targetType, boolean includeSourceNullCheck, boolean setExplicitlyToNull, - boolean setExplicitlyToDefault ) { + boolean setExplicitlyToDefault, + boolean mustCastForNull) { super( decoratedAssignment, fieldAssignment ); this.thrownTypesToExclude = thrownTypesToExclude; this.factoryMethod = factoryMethod; @@ -42,6 +44,7 @@ public UpdateWrapper( Assignment decoratedAssignment, this.includeSourceNullCheck = includeSourceNullCheck; this.setExplicitlyToDefault = setExplicitlyToDefault; this.setExplicitlyToNull = setExplicitlyToNull; + this.mustCastForNull = mustCastForNull; } private static Type determineImplType(Assignment factoryMethod, Type targetType) { @@ -54,7 +57,7 @@ private static Type determineImplType(Assignment factoryMethod, Type targetType) return targetType.getImplementationType(); } - // no factory method means we create a new instance ourself and thus need to import the type + // no factory method means we create a new instance ourselves and thus need to import the type return targetType; } @@ -74,8 +77,7 @@ public List getThrownTypes() { @Override public Set getImportTypes() { - Set imported = new HashSet<>(); - imported.addAll( super.getImportTypes() ); + Set imported = new HashSet<>( super.getImportTypes() ); if ( factoryMethod != null ) { imported.addAll( factoryMethod.getImportTypes() ); } @@ -101,4 +103,8 @@ public boolean isSetExplicitlyToNull() { public boolean isSetExplicitlyToDefault() { return setExplicitlyToDefault; } + + public boolean isMustCastForNull() { + return mustCastForNull; + } } 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..d013a77a7a --- /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..ced945bb90 --- /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/Assignment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Assignment.java index 3cd32c82ea..7ed0140d81 100644 --- 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 @@ -90,7 +90,7 @@ public boolean isConverted() { * * @return source reference */ - String getSourcePresenceCheckerReference(); + PresenceCheck getSourcePresenceCheckerReference(); /** * the source type used in the matching process @@ -147,7 +147,6 @@ public boolean isConverted() { */ void setSourceLoopVarName(String sourceLoopVarName); - /** * Returns whether the type of assignment * 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 index 94f406b1f9..9300f683ce 100644 --- 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 @@ -6,13 +6,17 @@ 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 javax.lang.model.util.Types; +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 { @@ -82,15 +86,8 @@ public Collection getBuildMethods() { return buildMethods; } - public BuilderInfo asBuilderInfo() { - return new BuilderInfo.Builder() - .builderCreationMethod( this.builderCreationMethod ) - .buildMethod( this.buildMethods ) - .build(); - } - public static BuilderType create(BuilderInfo builderInfo, Type typeToBuild, TypeFactory typeFactory, - Types typeUtils) { + TypeUtils typeUtils) { if ( builderInfo == null ) { return null; } @@ -109,6 +106,11 @@ else if ( typeUtils.isSameType( builder.getTypeMirror(), builderCreationOwner ) 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, 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 c2aa73f307..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 @@ -21,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. @@ -32,6 +39,8 @@ public interface ConversionContext { String getNumberFormat(); + String getLocale(); + TypeFactory getTypeFactory(); } 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 8e8c328f20..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 @@ -61,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; @@ -75,7 +70,7 @@ public DateFormatValidationResult validate(String dateFormat) { private static boolean isXmlGregorianCalendarSupposedToBeMapped(Type sourceType, Type targetType) { return typesEqualsOneOf( - sourceType, targetType, XmlConstants.JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR ); + sourceType, targetType, XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ); } private static boolean isJodaDateTimeSupposed(Type sourceType, Type targetType) { 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 f5a9fcc761..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 @@ -21,6 +21,7 @@ public class DefaultConversionContext implements ConversionContext { 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, @@ -32,6 +33,7 @@ public DefaultConversionContext(TypeFactory typeFactory, FormattingMessager mess this.formattingParameters = formattingParameters; this.dateFormat = this.formattingParameters.getDate(); this.numberFormat = this.formattingParameters.getNumber(); + this.locale = this.formattingParameters.getLocale(); validateDateFormat(); } @@ -59,11 +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/source/builtin/FinalField.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FinalField.java similarity index 77% rename from processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/FinalField.java rename to processor/src/main/java/org/mapstruct/ap/internal/model/common/FinalField.java index 5ce4167117..c3e1ef1f39 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/FinalField.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/FinalField.java @@ -3,16 +3,14 @@ * * 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.Type; +package org.mapstruct.ap.internal.model.common; /** * A mapper instance field, initialized as null * * @author Sjaak Derksen */ -public class FinalField implements BuiltInFieldReference { +public class FinalField implements FieldReference { private final Type type; private String variableName; 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 index e21f5d74fa..7ef818630a 100644 --- 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 @@ -10,26 +10,29 @@ 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 ); + 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) { + 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() { @@ -51,4 +54,8 @@ public AnnotationValue getDateAnnotationValue() { 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 index 45b73c492f..af8c7ecfb3 100644 --- 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 @@ -16,23 +16,34 @@ 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) { + 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 ); + return new ImplementationType( type, false, false, null ); } public static ImplementationType withInitialCapacity(Type type) { - return new ImplementationType( type, true, false ); + return new ImplementationType( type, true, false, null ); } public static ImplementationType withLoadFactorAdjustment(Type type) { - return new ImplementationType( type, true, true ); + return new ImplementationType( type, true, true, null ); + } + + public static ImplementationType withFactoryMethod(Type type, String factoryMethodName) { + return new ImplementationType( type, true, false, factoryMethodName ); } /** @@ -44,7 +55,7 @@ public static ImplementationType withLoadFactorAdjustment(Type type) { * @return a new implementation type with the given {@code type} */ public ImplementationType createNew(Type type) { - return new ImplementationType( type, initialCapacityConstructor, loadFactorAdjustment ); + return new ImplementationType( type, initialCapacityConstructor, loadFactorAdjustment, factoryMethodName ); } /** @@ -71,4 +82,8 @@ public boolean hasInitialCapacityConstructor() { public boolean isLoadFactorAdjustment() { return loadFactorAdjustment; } + + public String getFactoryMethodName() { + return factoryMethodName; + } } 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/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index e4dea46a4f..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 @@ -5,14 +5,18 @@ */ package org.mapstruct.ap.internal.model.common; -import java.util.ArrayList; 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.prism.ContextPrism; -import org.mapstruct.ap.internal.prism.MappingTargetPrism; -import org.mapstruct.ap.internal.prism.TargetTypePrism; +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; /** @@ -22,28 +26,57 @@ */ 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(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, + 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; + } + + 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 = 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, 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() { @@ -64,10 +97,20 @@ public boolean isMappingTarget() { @Override public String toString() { + return String.format( format(), type ); + } + + public String describe() { + return String.format( format(), type.describe() ); + } + + private String format() { return ( mappingTarget ? "@MappingTarget " : "" ) + ( targetType ? "@TargetType " : "" ) + ( mappingContext ? "@Context " : "" ) - + type.toString() + " " + name; + + ( sourcePropertyName ? "@SourcePropertyName " : "" ) + + ( targetPropertyName ? "@TargetPropertyName " : "" ) + + "%s " + name; } @Override @@ -83,10 +126,40 @@ 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 result = name != null ? name.hashCode() : 0; @@ -105,31 +178,31 @@ public boolean equals(Object o) { Parameter parameter = (Parameter) o; - if ( name != null ? !name.equals( parameter.name ) : parameter.name != null ) { + if ( !Objects.equals( name, parameter.name ) ) { return false; } - return type != null ? type.equals( parameter.type ) : parameter.type == null; + return Objects.equals( type, parameter.type ); } public static Parameter forElementAndType(VariableElement element, Type parameterType, boolean isVarArgs) { return new Parameter( - element.getSimpleName().toString(), + element, parameterType, - MappingTargetPrism.getInstanceOn( element ) != null, - TargetTypePrism.getInstanceOn( element ) != null, - ContextPrism.getInstanceOn( element ) != null, isVarArgs ); } public static Parameter forForgedMappingTarget(Type parameterType) { return new Parameter( + "mappingTarget", "mappingTarget", parameterType, true, false, false, + false, + false, false ); } @@ -139,15 +212,20 @@ public static Parameter forForgedMappingTarget(Type parameterType) { * @return the parameters from the given list that are considered 'source parameters' */ public static List getSourceParameters(List parameters) { - List sourceParameters = new ArrayList<>( parameters.size() ); - - for ( Parameter parameter : parameters ) { - if ( !parameter.isMappingTarget() && !parameter.isTargetType() && !parameter.isMappingContext() ) { - sourceParameters.add( parameter ); - } - } + return parameters.stream().filter( Parameter::isSourceParameter ).collect( Collectors.toList() ); + } - return sourceParameters; + /** + * @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 ); } /** @@ -155,34 +233,23 @@ public static List getSourceParameters(List parameters) { * @return the parameters from the given list that are marked as 'mapping context parameters' */ public static List getContextParameters(List parameters) { - List contextParameters = new ArrayList<>( parameters.size() ); - - for ( Parameter parameter : parameters ) { - if ( parameter.isMappingContext() ) { - contextParameters.add( parameter ); - } - } - - return contextParameters; + return parameters.stream().filter( Parameter::isMappingContext ).collect( Collectors.toList() ); } public static Parameter getMappingTargetParameter(List parameters) { - for ( Parameter parameter : parameters ) { - if ( parameter.isMappingTarget() ) { - return parameter; - } - } - - return null; + return parameters.stream().filter( Parameter::isMappingTarget ).findAny().orElse( null ); } public static Parameter getTargetTypeParameter(List parameters) { - for ( Parameter parameter : parameters ) { - if ( parameter.isTargetType() ) { - return parameter; - } - } + return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null ); + } - return 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 index 5f807578e8..c1a594c73a 100644 --- 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 @@ -6,7 +6,9 @@ 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; @@ -19,18 +21,14 @@ public class ParameterBinding { private final Type type; private final String variableName; - private final boolean targetType; - private final boolean mappingTarget; - private final boolean mappingContext; private final SourceRHS sourceRHS; + private final Collection bindingTypes; - private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType, - boolean mappingContext, SourceRHS sourceRHS) { + private ParameterBinding(Type parameterType, String variableName, Collection bindingTypes, + SourceRHS sourceRHS) { this.type = parameterType; this.variableName = variableName; - this.targetType = targetType; - this.mappingTarget = mappingTarget; - this.mappingContext = mappingContext; + this.bindingTypes = bindingTypes; this.sourceRHS = sourceRHS; } @@ -41,25 +39,47 @@ 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 targetType; + return bindingTypes.contains( BindingType.TARGET_TYPE ); } /** * @return {@code true}, if the parameter being bound is a {@code @MappingTarget} parameter. */ public boolean isMappingTarget() { - return mappingTarget; + return bindingTypes.contains( BindingType.MAPPING_TARGET ); } /** * @return {@code true}, if the parameter being bound is a {@code @MappingContext} parameter. */ public boolean isMappingContext() { - return mappingContext; + 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 ); } /** @@ -77,7 +97,7 @@ public SourceRHS getSourceRHS() { } public Set getImportTypes() { - if ( targetType ) { + if ( isTargetType() ) { return type.getImportTypes(); } @@ -93,12 +113,31 @@ public Set getImportTypes() { * @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(), - parameter.isMappingTarget(), - parameter.isTargetType(), - parameter.isMappingContext(), + bindingTypes, null ); } @@ -111,12 +150,45 @@ public static List fromParameters(List parameters) 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, false, true, false, null ); + 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 + ); } /** @@ -124,7 +196,7 @@ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { * @return a parameter binding representing a mapping target parameter */ public static ParameterBinding forMappingTargetBinding(Type resultType) { - return new ParameterBinding( resultType, null, true, false, false, null ); + return new ParameterBinding( resultType, null, Collections.singleton( BindingType.MAPPING_TARGET ), null ); } /** @@ -132,10 +204,27 @@ public static ParameterBinding forMappingTargetBinding(Type resultType) { * @return a parameter binding representing a mapping source type */ public static ParameterBinding forSourceTypeBinding(Type sourceType) { - return new ParameterBinding( sourceType, null, false, false, false, null ); + return new ParameterBinding( sourceType, null, Collections.singleton( BindingType.SOURCE_TYPE ), null ); } public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) { - return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, 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 index 5dc5d91832..b4d422c797 100644 --- 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 @@ -30,7 +30,7 @@ public class SourceRHS extends ModelElement implements Assignment { private String sourceLoopVarName; private final Set existingVariableNames; private final String sourceErrorMessagePart; - private final String sourcePresenceCheckerReference; + private PresenceCheck sourcePresenceCheckerReference; private boolean useElementAsSourceTypeForMatching = false; private final String sourceParameterName; @@ -39,7 +39,7 @@ public SourceRHS(String sourceReference, Type sourceType, Set existingVa this( sourceReference, sourceReference, null, sourceType, existingVariableNames, sourceErrorMessagePart ); } - public SourceRHS(String sourceParameterName, String sourceReference, String sourcePresenceCheckerReference, + public SourceRHS(String sourceParameterName, String sourceReference, PresenceCheck sourcePresenceCheckerReference, Type sourceType, Set existingVariableNames, String sourceErrorMessagePart ) { this.sourceReference = sourceReference; this.sourceType = sourceType; @@ -60,10 +60,14 @@ public boolean isSourceReferenceParameter() { } @Override - public String getSourcePresenceCheckerReference() { + public PresenceCheck getSourcePresenceCheckerReference() { return sourcePresenceCheckerReference; } + public void setSourcePresenceCheckerReference(PresenceCheck sourcePresenceCheckerReference) { + this.sourcePresenceCheckerReference = sourcePresenceCheckerReference; + } + @Override public Type getSourceType() { return sourceType; @@ -96,6 +100,10 @@ public void setSourceLoopVarName(String sourceLoopVarName) { @Override public Set getImportTypes() { + if ( sourcePresenceCheckerReference != null ) { + return sourcePresenceCheckerReference.getImportTypes(); + } + return Collections.emptySet(); } @@ -141,6 +149,12 @@ public Type getSourceTypeForMatching() { 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; } 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 5992a687bd..6287403ba3 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 @@ -5,44 +5,71 @@ */ 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.ElementFilter; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; +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.JavaStreamConstants; +import org.mapstruct.ap.internal.util.NativeTypes; import org.mapstruct.ap.internal.util.Nouns; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; -import org.mapstruct.ap.spi.BuilderInfo; - +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; -import org.mapstruct.ap.internal.util.NativeTypes; /** * Represents (a reference to) the type of a bean property, parameter etc. Types are managed per generated source file. @@ -53,11 +80,35 @@ * 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; @@ -67,10 +118,11 @@ public class Type extends ModelElement implements Comparable { private final ImplementationType implementationType; private final Type componentType; - private final BuilderType builderType; + private final Type topLevelType; private final String packageName; private final String name; + private final String nameWithTopLevelTypeName; private final String qualifiedName; private final boolean isInterface; @@ -82,37 +134,48 @@ public class Type extends ModelElement implements Comparable { private final boolean isStream; private final boolean isLiteral; + private final boolean loggingVerbose; + private final List enumConstants; private final Map toBeImportedTypes; private final Map notToBeImportedTypes; private Boolean isToBeImported; - private Map readAccessors = null; - private Map presenceCheckers = null; + private Map readAccessors = null; + private Map presenceCheckers = null; + + private List allMethods = null; + private List allFields = null; + private List recordComponents = null; - private List allAccessors = null; private List setters = null; private List adders = null; private List alternativeTargetAccessors = null; private Type boundingBase = null; + private List boundTypes = null; - private Boolean hasEmptyAccessibleContructor; + private Type boxedEquivalent = null; + + private Boolean hasAccessibleConstructor; + 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, ImplementationType implementationType, Type componentType, - BuilderInfo builderInfo, String packageName, String name, String qualifiedName, boolean isInterface, boolean isEnumType, boolean isIterableType, boolean isCollectionType, boolean isMapType, boolean isStreamType, Map toBeImportedTypes, Map notToBeImportedTypes, Boolean isToBeImported, - boolean isLiteral ) { + boolean isLiteral, boolean loggingVerbose) { this.typeUtils = typeUtils; this.elementUtils = elementUtils; @@ -157,7 +220,22 @@ public Type(Types typeUtils, Elements elementUtils, TypeFactory typeFactory, this.isToBeImported = isToBeImported; this.toBeImportedTypes = toBeImportedTypes; this.notToBeImportedTypes = notToBeImportedTypes; - this.builderType = BuilderType.create( builderInfo, this, this.typeFactory, this.typeUtils ); + 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 @@ -185,11 +263,33 @@ public String getName() { *

      * 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, otherwise the fully-qualified name. + * @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() { - return isToBeImported() ? name : ( shouldUseSimpleName() ? name : qualifiedName ); + 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() { @@ -200,18 +300,6 @@ public Type getComponentType() { return componentType; } - public BuilderType getBuilderType() { - return builderType; - } - - /** - * The effective type that should be used when searching for getters / setters, creating new types etc - * @return the effective type for mappings - */ - public Type getEffectiveType() { - return builderType != null ? builderType.getBuilder() : this; - } - public boolean isPrimitive() { return typeMirror.getKind().isPrimitive(); } @@ -232,6 +320,10 @@ public boolean isAbstract() { return typeElement != null && typeElement.getModifiers().contains( Modifier.ABSTRACT ); } + public boolean isString() { + return String.class.getName().equals( getFullyQualifiedName() ); + } + /** * @return this type's enum constants in case it is an enum, an empty list otherwise. */ @@ -282,6 +374,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; } @@ -290,10 +393,52 @@ 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 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}. * @@ -303,7 +448,12 @@ public boolean isStreamType() { return isStream; } - public boolean isWildCardSuperBound() { + /** + * 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. ? super Number) + */ + public boolean hasSuperBound() { boolean result = false; if ( typeMirror.getKind() == TypeKind.WILDCARD ) { WildcardType wildcardType = (WildcardType) typeMirror; @@ -312,7 +462,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 super bound (e.g. ? extends Number) + */ + public boolean hasExtendsBound() { boolean result = false; if ( typeMirror.getKind() == TypeKind.WILDCARD ) { WildcardType wildcardType = (WildcardType) typeMirror; @@ -321,6 +476,40 @@ 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; } @@ -344,17 +533,25 @@ public Set getImportTypes() { result.addAll( componentType.getImportTypes() ); } + if ( topLevelType != null ) { + result.addAll( topLevelType.getImportTypes() ); + } + for ( Type parameter : typeParameters ) { result.addAll( parameter.getImportTypes() ); } - if ( ( isWildCardExtendsBound() || isWildCardSuperBound() ) && getTypeBound() != null ) { + if ( ( hasExtendsBound() || hasSuperBound() ) && getTypeBound() != null ) { result.addAll( getTypeBound().getImportTypes() ); } return result; } + protected boolean isTopLevelTypeToBeImported() { + return topLevelType != null && topLevelType.isToBeImported(); + } + /** * 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). @@ -377,7 +574,7 @@ public boolean isToBeImported() { isToBeImported = true; } } - else { + else if ( typeElement == null || !typeElement.getNestingKind().isNested() ) { toBeImportedTypes.put( trimmedName, trimmedQualifiedName ); isToBeImported = true; } @@ -386,26 +583,11 @@ public boolean isToBeImported() { } private boolean shouldUseSimpleName() { - String fqn = notToBeImportedTypes.get( name ); - return this.qualifiedName.equals( fqn ); - } - - /** - * @param annotationTypeName the fully qualified name of the annotation type - * - * @return true, if the type is annotated with an annotation of the specified type (super-types are not inspected) - */ - public boolean isAnnotatedWith(String annotationTypeName) { - List annotationMirrors = typeElement.getAnnotationMirrors(); - - for ( AnnotationMirror mirror : annotationMirrors ) { - Name mirrorAnnotationName = ( (TypeElement) mirror.getAnnotationType().asElement() ).getQualifiedName(); - if ( mirrorAnnotationName.contentEquals( annotationTypeName ) ) { - return true; - } - } - - return false; + // 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() { @@ -419,7 +601,6 @@ public Type erasure() { typeParameters, implementationType, componentType, - builderType == null ? null : builderType.asBuilderInfo(), packageName, name, qualifiedName, @@ -432,7 +613,8 @@ public Type erasure() { toBeImportedTypes, notToBeImportedTypes, isToBeImported, - isLiteral + isLiteral, + loggingVerbose ); } @@ -462,7 +644,6 @@ public Type withoutBounds() { bounds, implementationType, componentType, - builderType == null ? null : builderType.asBuilderInfo(), packageName, name, qualifiedName, @@ -475,27 +656,99 @@ public Type withoutBounds() { toBeImportedTypes, notToBeImportedTypes, isToBeImported, - isLiteral + isLiteral, + loggingVerbose ); } + private Type replaceGeneric(Type oldGenericType, Type newType) { + if ( !typeParameters.contains( oldGenericType ) || newType == null ) { + 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 super wild card types into account; - // e.g. Number wouldn't be assignable to ? super Number atm. (is there any practical use case) 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; + } + } - TypeMirror typeMirrorToMatch = isWildCardExtendsBound() ? getTypeBound().typeMirror : typeMirror; + 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( typeMirrorToMatch, other.typeMirror ); + Map presenceCheckers = getPropertyPresenceCheckers(); + return presenceCheckers.get( propertyName ); } /** @@ -503,33 +756,49 @@ public boolean isAssignableTo(Type other) { * * @return an unmodifiable map of all read accessors (including 'is' for booleans), indexed by property name */ - public Map getPropertyReadAccessors() { + public Map getPropertyReadAccessors() { if ( readAccessors == null ) { - List getterList = Filters.getterMethodsIn( accessorNaming, getAllAccessors() ); - Map modifiableGetters = new LinkedHashMap<>(); - for ( Accessor getter : getterList ) { - String propertyName = accessorNaming.getPropertyName( getter ); + + 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 ( !getter.getSimpleName().toString().startsWith( "is" ) ) { - modifiableGetters.put( accessorNaming.getPropertyName( getter ), getter ); + if ( !simpleName.startsWith( "is" ) ) { + modifiableGetters.put( propertyName, getter ); } } else { - modifiableGetters.put( accessorNaming.getPropertyName( getter ), getter ); + modifiableGetters.put( propertyName, getter ); } } - List fieldsList = Filters.fieldsIn( getAllAccessors() ); - for ( Accessor field : fieldsList ) { - String propertyName = accessorNaming.getPropertyName( field ); - if ( !modifiableGetters.containsKey( propertyName ) ) { - // 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.put( propertyName, field ); - } + 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 ); } @@ -541,15 +810,15 @@ public Map getPropertyReadAccessors() { * * @return an unmodifiable map of all presence checkers, indexed by property name */ - public Map getPropertyPresenceCheckers() { + public Map getPropertyPresenceCheckers() { if ( presenceCheckers == null ) { - List checkerList = Filters.presenceCheckMethodsIn( - accessorNaming, - getAllAccessors() - ); - Map modifiableCheckers = new LinkedHashMap<>(); - for ( ExecutableElementAccessor checker : checkerList ) { - modifiableCheckers.put( accessorNaming.getPropertyName( checker ), checker ); + List checkerList = filters.presenceCheckMethodsIn( getAllMethods() ); + Map modifiableCheckers = new LinkedHashMap<>(); + for ( ExecutableElement checker : checkerList ) { + modifiableCheckers.put( + getPropertyName( checker ), + PresenceCheckAccessor.methodInvocation( checker ) + ); } presenceCheckers = Collections.unmodifiableMap( modifiableCheckers ); } @@ -569,7 +838,11 @@ public Map getPropertyPresenceCheckers() { * @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<>( getSetters() ); candidates.addAll( getAlternativeTargetAccessors() ); @@ -577,7 +850,7 @@ public Map getPropertyWriteAccessors( CollectionMappingStrateg Map result = new LinkedHashMap<>(); for ( Accessor candidate : candidates ) { - String targetPropertyName = accessorNaming.getPropertyName( candidate ); + String targetPropertyName = getPropertyName( candidate ); Accessor readAccessor = getPropertyReadAccessors().get( targetPropertyName ); @@ -587,18 +860,18 @@ public Map getPropertyWriteAccessors( CollectionMappingStrateg // 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 - || cmStrategy == CollectionMappingStrategyPrism.TARGET_IMMUTABLE ) { + if ( cmStrategy == CollectionMappingStrategyGem.SETTER_PREFERRED + || cmStrategy == CollectionMappingStrategyGem.ADDER_PREFERRED + || cmStrategy == CollectionMappingStrategyGem.TARGET_IMMUTABLE ) { // first check if there's a setter method. Accessor adderMethod = null; - if ( accessorNaming.isSetterMethod( candidate ) + if ( candidate.getAccessorType() == AccessorType.SETTER // ok, the current accessor is a setter. So now the strategy determines what to use - && cmStrategy == CollectionMappingStrategyPrism.ADDER_PREFERRED ) { + && cmStrategy == CollectionMappingStrategyGem.ADDER_PREFERRED ) { adderMethod = getAdderForType( targetType, targetPropertyName ); } - else if ( accessorNaming.isGetterMethod( candidate ) ) { + 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. adderMethod = getAdderForType( targetType, targetPropertyName ); @@ -607,13 +880,24 @@ else if ( accessorNaming.isGetterMethod( candidate ) ) { // an adder has been found (according strategy) so overrule current choice. candidate = adderMethod; } + } - else if ( Executables.isFieldAccessor( candidate ) && ( Executables.isFinal( candidate ) || + 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; + } + } + Accessor previousCandidate = result.get( targetPropertyName ); if ( previousCandidate == null || preferredType == null || ( targetType != null && typeUtils.isAssignable( preferredType.getTypeMirror(), targetType.getTypeMirror() ) ) ) { @@ -624,6 +908,14 @@ else if ( Executables.isFieldAccessor( candidate ) && ( Executables.isFinal( can return result; } + public List getRecordComponents() { + if ( recordComponents == null ) { + recordComponents = nullSafeTypeElementListConversion( filters::recordComponentsIn ); + } + + return recordComponents; + } + private Type determinePreferredType(Accessor readAccessor) { if ( readAccessor != null ) { return typeFactory.getReturnType( (DeclaredType) typeMirror, readAccessor ); @@ -636,18 +928,49 @@ private Type determineTargetType(Accessor candidate) { if ( parameter != null ) { return parameter.getType(); } - else if ( accessorNaming.isGetterMethod( candidate ) || Executables.isFieldAccessor( candidate ) ) { + else if ( candidate.getAccessorType() == AccessorType.GETTER + || candidate.getAccessorType().isFieldAssignment() ) { return typeFactory.getReturnType( (DeclaredType) typeMirror, candidate ); } return null; } - private List getAllAccessors() { - if ( allAccessors == null ) { - allAccessors = Executables.getAllEnclosedAccessors( elementUtils, typeElement ); + private List getAllMethods() { + if ( allMethods == null ) { + allMethods = nullSafeTypeElementListConversion( elementUtils::getAllEnclosedExecutableElements ); + } + + return allMethods; + } + + private List getAllFields() { + if ( allFields == null ) { + allFields = nullSafeTypeElementListConversion( elementUtils::getAllEnclosedFields ); } - return allAccessors; + 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 ); } /** @@ -709,21 +1032,15 @@ else if ( collectionProperty.isStreamType() ) { * @return accessor candidates */ private List getAccessorCandidates(Type property, Class superclass) { - TypeMirror typeArg = first( property.determineTypeArguments( superclass ) ).getTypeBound() - .getTypeMirror(); + 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 ) { - ExecutableElement executable = adder.getExecutable(); - if ( executable == null ) { - // it should not be null, but to be safe - continue; - } - VariableElement arg = executable.getParameters().get( 0 ); - if ( typeUtils.isSameType( boxed( arg.asType() ), boxed( typeArg ) ) ) { + TypeMirror adderParameterType = determineTargetType( adder ).getTypeMirror(); + if ( typeUtils.isSameType( boxed( adderParameterType ), boxed( typeArg ) ) ) { candidateList.add( adder ); } } @@ -744,9 +1061,9 @@ private TypeMirror boxed(TypeMirror possiblePrimitive) { * * @return an unmodifiable list of all setters */ - private List getSetters() { + public List getSetters() { if ( setters == null ) { - setters = Collections.unmodifiableList( Filters.setterMethodsIn( accessorNaming, getAllAccessors() ) ); + setters = Collections.unmodifiableList( filters.setterMethodsIn( getAllMethods() ) ); } return setters; } @@ -761,7 +1078,7 @@ private List getSetters() { */ private List getAdders() { if ( adders == null ) { - adders = Collections.unmodifiableList( Filters.adderMethodsIn( accessorNaming, getAllAccessors() ) ); + adders = Collections.unmodifiableList( filters.adderMethodsIn( getAllMethods() ) ); } return adders; } @@ -776,15 +1093,21 @@ private List getAdders() { * @return an unmodifiable list of alternative target accessors. */ private List getAlternativeTargetAccessors() { + if ( alternativeTargetAccessors != null ) { + return alternativeTargetAccessors; + } + + if ( isRecord() ) { + alternativeTargetAccessors = Collections.emptyList(); + } if ( alternativeTargetAccessors == null ) { List result = new ArrayList<>(); List setterMethods = getSetters(); - List readAccessors = - new ArrayList<>( getPropertyReadAccessors().values() ); + List readAccessors = new ArrayList<>( getPropertyReadAccessors().values() ); // All the fields are also alternative accessors - readAccessors.addAll( Filters.fieldsIn( getAllAccessors() ) ); + readAccessors.addAll( filters.fieldsIn( getAllFields(), ElementAccessor::new ) ); // 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. @@ -794,7 +1117,7 @@ private List getAlternativeTargetAccessors() { !correspondingSetterMethodExists( readAccessor, setterMethods ) ) { result.add( readAccessor ); } - else if ( Executables.isFieldAccessor( readAccessor ) && + else if ( readAccessor.getAccessorType() == AccessorType.FIELD && !correspondingSetterMethodExists( readAccessor, setterMethods ) ) { result.add( readAccessor ); } @@ -807,10 +1130,10 @@ else if ( Executables.isFieldAccessor( readAccessor ) && private boolean correspondingSetterMethodExists(Accessor getterMethod, List setterMethods) { - String getterPropertyName = accessorNaming.getPropertyName( getterMethod ); + String getterPropertyName = getPropertyName( getterMethod ); for ( Accessor setterMethod : setterMethods ) { - String setterPropertyName = accessorNaming.getPropertyName( setterMethod ); + String setterPropertyName = getPropertyName( setterMethod ); if ( getterPropertyName.equals( setterPropertyName ) ) { return true; } @@ -831,7 +1154,7 @@ private boolean isCollection(TypeMirror candidate) { 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.isSubtype( candidate, streamType ); + return streamType != null && typeUtils.isSubtypeErased( candidate, streamType ); } private boolean isMap(TypeMirror candidate) { @@ -841,7 +1164,7 @@ private boolean isMap(TypeMirror candidate) { private boolean isSubType(TypeMirror candidate, Class clazz) { String className = clazz.getCanonicalName(); TypeMirror classType = typeUtils.erasure( elementUtils.getTypeElement( className ).asType() ); - return typeUtils.isSubtype( candidate, classType ); + return typeUtils.isSubtypeErased( candidate, classType ); } /** @@ -903,6 +1226,10 @@ else if ( !method.getModifiers().contains( Modifier.PUBLIC ) ) { * FTL. */ public String getNull() { + if ( isOptionalType() ) { + return createReferenceName() + ".empty()"; + } + if ( !isPrimitive() || isArrayType() ) { return "null"; } @@ -975,7 +1302,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 @@ -988,6 +1322,36 @@ 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; + } + /** * * @return an identification that can be used as part in a forged method name. @@ -1021,20 +1385,71 @@ public Type getTypeBound() { return boundingBase; } - public boolean hasEmptyAccessibleContructor() { + 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; + + } - if ( this.hasEmptyAccessibleContructor == null ) { - hasEmptyAccessibleContructor = false; + public boolean hasAccessibleConstructor() { + if ( hasAccessibleConstructor == null ) { + hasAccessibleConstructor = false; List constructors = ElementFilter.constructorsIn( typeElement.getEnclosedElements() ); for ( ExecutableElement constructor : constructors ) { - if ( !constructor.getModifiers().contains( Modifier.PRIVATE ) - && constructor.getParameters().isEmpty() ) { - hasEmptyAccessibleContructor = true; + if ( !constructor.getModifiers().contains( Modifier.PRIVATE ) ) { + hasAccessibleConstructor = true; break; } } } - return hasEmptyAccessibleContructor; + return hasAccessibleConstructor; + } + + 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() ); } /** @@ -1072,6 +1487,333 @@ 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 ); + } + } + 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 ); + ResolvedPair result = visit( parameterizedTypeArg, declaredTypeArg ); + if ( result != super.DEFAULT_VALUE ) { + results.add( result ); + } + } + } + else { + // 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; + } + } + + private boolean isJavaLangObject(TypeMirror type) { + if ( type instanceof DeclaredType ) { + return ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName() + .contentEquals( Object.class.getName() ); + } + 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}. * @@ -1096,4 +1838,209 @@ private String trimSimpleClassName(String className) { 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 + ); + } + } + } 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 567053986e..606bbce69d 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 @@ -10,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,25 +38,29 @@ 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.gem.BuilderGem; import org.mapstruct.ap.internal.util.AnnotationProcessingException; import org.mapstruct.ap.internal.util.Collections; +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; @@ -66,15 +72,24 @@ public class TypeFactory { private static final Extractor BUILDER_INFO_CREATION_METHOD_EXTRACTOR = - new Extractor() { - @Override - public String apply(BuilderInfo builderInfo) { - return builderInfo.getBuilderCreationMethod().toString(); + 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 Elements elementUtils; - private final Types typeUtils; + private final ElementUtils elementUtils; + private final TypeUtils typeUtils; private final FormattingMessager messager; private final RoundContext roundContext; @@ -87,8 +102,11 @@ public String apply(BuilderInfo builderInfo) { private final Map toBeImportedTypes = new HashMap<>(); private final Map notToBeImportedTypes; - public TypeFactory(Elements elementUtils, Types typeUtils, FormattingMessager messager, RoundContext roundContext, - Map notToBeImportedTypes) { + 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; @@ -106,11 +124,22 @@ public TypeFactory(Elements elementUtils, Types typeUtils, FormattingMessager me implementationTypes.put( Collection.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); implementationTypes.put( List.class.getName(), withInitialCapacity( getType( ArrayList.class ) ) ); - implementationTypes.put( Set.class.getName(), withLoadFactorAdjustment( getType( HashSet.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(), withLoadFactorAdjustment( getType( HashMap.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( @@ -121,6 +150,16 @@ public TypeFactory(Elements elementUtils, Types typeUtils, FormattingMessager me 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 ) + ); + + this.loggingVerbose = loggingVerbose; } public Type getTypeForLiteral(Class type) { @@ -148,7 +187,6 @@ private Type getType(String canonicalName, boolean isLiteral) { return getType( typeElement, isLiteral ); } - /** * Determines if the type with the given full qualified name is part of the classpath * @@ -180,18 +218,34 @@ public Type getType(TypeMirror 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 ); } ImplementationType implementationType = getImplementationType( mirror ); - BuilderInfo builderInfo = findBuilder( mirror ); - boolean isIterableType = typeUtils.isSubtype( mirror, iterableType ); - boolean isCollectionType = typeUtils.isSubtype( mirror, collectionType ); - boolean isMapType = typeUtils.isSubtype( mirror, mapType ); - boolean isStreamType = streamType != null && typeUtils.isSubtype( mirror, streamType ); + 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; @@ -200,7 +254,7 @@ private Type getType(TypeMirror mirror, boolean isLiteral) { String qualifiedName; TypeElement typeElement; Type componentType; - Boolean toBeImported = null; + Boolean toBeImported = alwaysImport; if ( mirror.getKind() == TypeKind.DECLARED ) { DeclaredType declaredType = (DeclaredType) mirror; @@ -264,7 +318,19 @@ else if (componentTypeMirror.getKind().isPrimitive()) { 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; @@ -280,7 +346,6 @@ else if (componentTypeMirror.getKind().isPrimitive()) { getTypeParameters( mirror, false ), implementationType, componentType, - builderInfo, packageName, name, qualifiedName, @@ -293,7 +358,8 @@ else if (componentTypeMirror.getKind().isPrimitive()) { toBeImportedTypes, notToBeImportedTypes, toBeImported, - isLiteral + isLiteral, + loggingVerbose ); } @@ -354,10 +420,10 @@ public TypeMirror getMethodType(DeclaredType includingType, Element method) { } public Parameter getSingleParameter(DeclaredType includingType, Accessor method) { - ExecutableElement executable = method.getExecutable(); - if ( executable == null ) { + if ( method.getAccessorType().isFieldAssignment() ) { return null; } + ExecutableElement executable = (ExecutableElement) method.getElement(); List parameters = executable.getParameters(); if ( parameters.size() != 1 ) { @@ -369,12 +435,16 @@ public Parameter getSingleParameter(DeclaredType includingType, Accessor method) } public List getParameters(DeclaredType includingType, Accessor accessor) { - ExecutableElement method = accessor.getExecutable(); - TypeMirror methodType = getMethodType( includingType, accessor.getElement() ); + 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( (ExecutableType) methodType, method ); + return getParameters( methodType, method ); } public List getParameters(ExecutableType methodType, ExecutableElement method) { @@ -385,7 +455,7 @@ public List getParameters(ExecutableType methodType, ExecutableElemen Iterator varIt = parameters.iterator(); Iterator typesIt = parameterTypes.iterator(); - for ( ; varIt.hasNext(); ) { + while ( varIt.hasNext() ) { VariableElement parameter = varIt.next(); TypeMirror parameterType = typesIt.next(); @@ -427,10 +497,14 @@ public List getThrownTypes(ExecutableType method) { } public List getThrownTypes(Accessor accessor) { - if (accessor.getExecutable() == null) { + if (accessor.getAccessorType().isFieldAssignment()) { return new ArrayList<>(); } - return extractTypes( accessor.getExecutable().getThrownTypes() ); + Element element = accessor.getElement(); + if ( element instanceof ExecutableElement ) { + return extractTypes( ( (ExecutableElement) element ).getThrownTypes() ); + } + return new ArrayList<>(); } private List extractTypes(List typeMirrors) { @@ -449,15 +523,12 @@ private List getTypeParameters(TypeMirror mirror, boolean isImplementation } 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; @@ -502,7 +573,6 @@ private ImplementationType getImplementationType(TypeMirror mirror) { getTypeParameters( mirror, true ), null, null, - null, implementationType.getPackageName(), implementationType.getName(), implementationType.getFullyQualifiedName(), @@ -515,7 +585,8 @@ private ImplementationType getImplementationType(TypeMirror mirror) { toBeImportedTypes, notToBeImportedTypes, null, - implementationType.isLiteral() + implementationType.isLiteral(), + loggingVerbose ); return implementation.createNew( replacement ); } @@ -523,19 +594,24 @@ private ImplementationType getImplementationType(TypeMirror mirror) { return null; } - private BuilderInfo findBuilder(TypeMirror type) { + 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 ) { - messager.printMessage( - typeUtils.asElement( type ), - Message.BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD, - type, - Strings.join( ex.getBuilderInfo(), ", ", BUILDER_INFO_CREATION_METHOD_EXTRACTOR ) - ); + 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; @@ -550,38 +626,6 @@ private TypeMirror getComponentType(TypeMirror mirror) { return arrayType.getComponentType(); } - /** - * 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. - */ - 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(); - } - - if ( collectionOrMap.isCollectionType() - && !"java.util.Collection".equals( collectionOrMap.getFullyQualifiedName() ) ) { - return getType( typeUtils.getDeclaredType( - elementUtils.getTypeElement( "java.util.Collection" ), - originalParameterMirrors ) ); - } - else if ( collectionOrMap.isMapType() - && !"java.util.Map".equals( collectionOrMap.getFullyQualifiedName() ) ) { - return getType( typeUtils.getDeclaredType( - elementUtils.getTypeElement( "java.util.Map" ), - originalParameterMirrors ) ); - } - - return collectionOrMap; - } - /** * creates a void return type * @@ -624,13 +668,16 @@ else if ( typeMirror.getKind() == TypeKind.TYPEVAR ) { if ( typeVariableType.getUpperBound() != null ) { return typeVariableType.getUpperBound(); } - // Lowerbounds intentionally left out: Type variables otherwise have a lower bound of NullType. + // 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; } - /** * 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 @@ -663,4 +710,21 @@ private boolean canBeProcessed(TypeMirror 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 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/dependency/GraphAnalyzer.java b/processor/src/main/java/org/mapstruct/ap/internal/model/dependency/GraphAnalyzer.java index 19e6374d55..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 @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -114,18 +115,18 @@ public static class GraphAnalyzerBuilder { 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 ) ) ); } /** @@ -139,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 445b01692c..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 @@ -7,6 +7,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * A node of a directed graph. @@ -86,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/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 f2dc9527d1..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 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 javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Types; - -import org.mapstruct.ap.internal.prism.BeanMappingPrism; -import org.mapstruct.ap.internal.prism.BuilderPrism; -import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; -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 SelectionParameters selectionParameters; - private final NullValueMappingStrategyPrism nullValueMappingStrategy; - private final NullValueCheckStrategyPrism nullValueCheckStrategy; - private final ReportingPolicyPrism reportingPolicy; - private final boolean ignoreByDefault; - private final List ignoreUnmappedSourceProperties; - private final BuilderPrism builder; - private final NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy; - - /** - * creates a mapping for inheritance. Will set ignoreByDefault to false. - * - * @param map - * @return - */ - public static BeanMapping forInheritance( BeanMapping map ) { - return new BeanMapping( - map.selectionParameters, - map.nullValueMappingStrategy, - map.nullValuePropertyMappingStrategy, - map.nullValueCheckStrategy, - map.reportingPolicy, - false, - map.ignoreUnmappedSourceProperties, - map.builder - ); - } - - public static BeanMapping fromPrism(BeanMappingPrism beanMapping, ExecutableElement method, - FormattingMessager messager, Types typeUtils) { - - if ( beanMapping == null ) { - return null; - } - - boolean resultTypeIsDefined = !TypeKind.VOID.equals( beanMapping.resultType().getKind() ); - - NullValueMappingStrategyPrism nullValueMappingStrategy = - null == beanMapping.values.nullValueMappingStrategy() - ? null - : NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() ); - - NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy = - null == beanMapping.values.nullValuePropertyMappingStrategy() - ? null - : NullValuePropertyMappingStrategyPrism.valueOf( beanMapping.nullValuePropertyMappingStrategy() ); - - NullValueCheckStrategyPrism nullValueCheckStrategy = - null == beanMapping.values.nullValueCheckStrategy() - ? null - : NullValueCheckStrategyPrism.valueOf( beanMapping.nullValueCheckStrategy() ); - - boolean ignoreByDefault = beanMapping.ignoreByDefault(); - BuilderPrism builderMapping = null; - if ( beanMapping.values.builder() != null ) { - builderMapping = beanMapping.builder(); - } - - if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() && beanMapping.qualifiedByName().isEmpty() - && beanMapping.ignoreUnmappedSourceProperties().isEmpty() - && ( nullValueMappingStrategy == null ) && ( nullValuePropertyMappingStrategy == null ) - && ( nullValueCheckStrategy == null ) && !ignoreByDefault && builderMapping == null ) { - - messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS ); - } - - SelectionParameters cmp = new SelectionParameters( - beanMapping.qualifiedBy(), - beanMapping.qualifiedByName(), - resultTypeIsDefined ? beanMapping.resultType() : null, - typeUtils - ); - - //TODO Do we want to add the reporting policy to the BeanMapping as well? To give more granular support? - return new BeanMapping( - cmp, - nullValueMappingStrategy, - nullValuePropertyMappingStrategy, - nullValueCheckStrategy, - null, - ignoreByDefault, - beanMapping.ignoreUnmappedSourceProperties(), - builderMapping - ); - } - - /** - * This method should be used to generate BeanMappings for our internal generated Mappings. Like forged update - * methods. - * - * @return bean mapping that needs to be used for Mappings - */ - public static BeanMapping forForgedMethods() { - return new BeanMapping( - null, - null, - null, - null, - ReportingPolicyPrism.IGNORE, - false, - Collections.emptyList(), - null - ); - } - - private BeanMapping(SelectionParameters selectionParameters, NullValueMappingStrategyPrism nvms, - NullValuePropertyMappingStrategyPrism nvpms, NullValueCheckStrategyPrism nvcs, - ReportingPolicyPrism reportingPolicy, boolean ignoreByDefault, - List ignoreUnmappedSourceProperties, BuilderPrism builder) { - this.selectionParameters = selectionParameters; - this.nullValueMappingStrategy = nvms; - this.nullValuePropertyMappingStrategy = nvpms; - this.nullValueCheckStrategy = nvcs; - this.reportingPolicy = reportingPolicy; - this.ignoreByDefault = ignoreByDefault; - this.ignoreUnmappedSourceProperties = ignoreUnmappedSourceProperties; - this.builder = builder; - } - - public SelectionParameters getSelectionParameters() { - return selectionParameters; - } - - public NullValueMappingStrategyPrism getNullValueMappingStrategy() { - return nullValueMappingStrategy; - } - - public NullValuePropertyMappingStrategyPrism getNullValuePropertyMappingStrategy() { - return nullValuePropertyMappingStrategy; - } - - public NullValueCheckStrategyPrism getNullValueCheckStrategy() { - return nullValueCheckStrategy; - } - - public ReportingPolicyPrism getReportingPolicy() { - return reportingPolicy; - } - - public boolean isignoreByDefault() { - return ignoreByDefault; - } - - public List getIgnoreUnmappedSourceProperties() { - return ignoreUnmappedSourceProperties; - } - - public BuilderPrism getBuilder() { - return builder; - } -} 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..8d76e65c75 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DefaultOptions.java @@ -0,0 +1,180 @@ +/* + * Copyright 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.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() ); + } + + 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..34478969fa --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/DelegatingOptions.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.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.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(); + } + + // 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 2c13344d20..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/EnumMapping.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 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; - -/** - * 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 4aeb2b5d59..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * Copyright 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.Iterator; -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; - private final ForgedMethodHistory history; - - private final List sourceParameters; - private final List contextParameters; - private final Parameter mappingTargetParameter; - private final MappingOptions mappingOptions; - private final ParameterProvidedMethods contextProvidedMethods; - private final boolean forgedNameBased; - - /** - * Creates a new forged method with the given name. - * - * @param name the (unique name) for this method - * @param sourceType the source type - * @param returnType the return type. - * @param mapperConfiguration the mapper configuration - * @param positionHintElement element used to for reference to the position in the source file. - * @param additionalParameters additional parameters to add to the forged method - * @param parameterProvidedMethods additional factory/lifecycle methods to consider that are provided by context - * parameters - */ - public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, - ExecutableElement positionHintElement, List additionalParameters, - ParameterProvidedMethods parameterProvidedMethods) { - this( - name, - sourceType, - returnType, - mapperConfiguration, - positionHintElement, - additionalParameters, - parameterProvidedMethods, - null, - null, - false ); - } - - /** - * Creates a new forged method with the given name. - * - * @param name the (unique name) for this method - * @param sourceType the source type - * @param returnType the return type. - * @param mapperConfiguration the mapper configuration - * @param positionHintElement element used to for reference to the position in the source file. - * @param additionalParameters additional parameters to add to the forged method - * @param parameterProvidedMethods additional factory/lifecycle methods to consider that are provided by context - * parameters - * @param history a parent forged method if this is a forged method within a forged method - * @param mappingOptions the mapping options for this method - * @param forgedNameBased forges a name based (matched) mapping method - */ - public ForgedMethod(String name, Type sourceType, Type returnType, MapperConfiguration mapperConfiguration, - ExecutableElement positionHintElement, List additionalParameters, - ParameterProvidedMethods parameterProvidedMethods, ForgedMethodHistory history, - MappingOptions mappingOptions, boolean forgedNameBased) { - String sourceParamName = Strings.decapitalize( sourceType.getName() ); - String sourceParamSafeName = Strings.getSafeVariableName( sourceParamName ); - - 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.contextProvidedMethods = parameterProvidedMethods; - - this.returnType = returnType; - this.thrownTypes = new ArrayList<>(); - this.name = Strings.sanitizeIdentifierName( name ); - this.mapperConfiguration = mapperConfiguration; - this.positionHintElement = positionHintElement; - this.history = history; - this.mappingOptions = mappingOptions == null ? MappingOptions.empty() : mappingOptions; - this.mappingOptions.initWithParameter( sourceParameter ); - this.forgedNameBased = forgedNameBased; - } - - /** - * 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.history = forgedMethod.history; - - this.sourceParameters = Parameter.getSourceParameters( parameters ); - this.contextParameters = Parameter.getContextParameters( parameters ); - this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); - this.mappingOptions = forgedMethod.mappingOptions; - this.contextProvidedMethods = forgedMethod.contextProvidedMethods; - - this.name = name; - this.forgedNameBased = forgedMethod.forgedNameBased; - } - - @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 contextProvidedMethods; - } - - @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 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; - } - - /** - * object factory mechanism not supported for forged methods - * - * @return false - */ - @Override - public boolean isObjectFactory() { - return false; - } - - @Override - public MappingOptions getMappingOptions() { - return mappingOptions; - } - - @Override - public boolean equals(Object o) { - if ( this == o ) { - return true; - } - if ( o == null || getClass() != o.getClass() ) { - return false; - } - - ForgedMethod that = (ForgedMethod) o; - - if ( parameters != null ? !parameters.equals( that.parameters ) : that.parameters != null ) { - return false; - } - return returnType != null ? returnType.equals( that.returnType ) : that.returnType == null; - - } - - @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/source/IterableMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java deleted file mode 100644 index 0f000ec74c..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 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 javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Types; - -import org.mapstruct.ap.internal.model.common.FormattingParameters; -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 SelectionParameters selectionParameters; - private final FormattingParameters formattingParameters; - private final AnnotationMirror mirror; - private final NullValueMappingStrategyPrism nullValueMappingStrategy; - - public static IterableMapping fromPrism(IterableMappingPrism iterableMapping, ExecutableElement method, - FormattingMessager messager, Types typeUtils) { - 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.numberFormat().isEmpty() - && iterableMapping.qualifiedBy().isEmpty() - && iterableMapping.qualifiedByName().isEmpty() - && ( nullValueMappingStrategy == null ) ) { - - messager.printMessage( method, Message.ITERABLEMAPPING_NO_ELEMENTS ); - } - - SelectionParameters selection = new SelectionParameters( - iterableMapping.qualifiedBy(), - iterableMapping.qualifiedByName(), - elementTargetTypeIsDefined ? iterableMapping.elementTargetType() : null, - typeUtils - ); - - FormattingParameters formatting = new FormattingParameters( - iterableMapping.dateFormat(), - iterableMapping.numberFormat(), - iterableMapping.mirror, - iterableMapping.values.dateFormat(), - method - ); - - return new IterableMapping( formatting, - selection, - iterableMapping.mirror, - nullValueMappingStrategy - ); - } - - private IterableMapping(FormattingParameters formattingParameters, SelectionParameters selectionParameters, - AnnotationMirror mirror, NullValueMappingStrategyPrism nvms) { - - this.formattingParameters = formattingParameters; - this.selectionParameters = selectionParameters; - this.mirror = mirror; - this.nullValueMappingStrategy = nvms; - } - - public SelectionParameters getSelectionParameters() { - return selectionParameters; - } - - public FormattingParameters getFormattingParameters() { - return formattingParameters; - } - - public AnnotationMirror getMirror() { - return mirror; - } - - 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 5bbc704d30..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMapping.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 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 javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Types; - -import org.mapstruct.ap.internal.model.common.FormattingParameters; -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 SelectionParameters keySelectionParameters; - private final SelectionParameters valueSelectionParameters; - private final FormattingParameters keyFormattingParameters; - private final FormattingParameters valueFormattingParameters; - private final AnnotationMirror mirror; - private final NullValueMappingStrategyPrism nullValueMappingStrategy; - - public static MapMapping fromPrism(MapMappingPrism mapMapping, ExecutableElement method, - FormattingMessager messager, Types typeUtils) { - 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.keyNumberFormat().isEmpty() - && mapMapping.keyQualifiedBy().isEmpty() - && mapMapping.keyQualifiedByName().isEmpty() - && mapMapping.valueDateFormat().isEmpty() - && mapMapping.valueNumberFormat().isEmpty() - && mapMapping.valueQualifiedBy().isEmpty() - && mapMapping.valueQualifiedByName().isEmpty() - && !keyTargetTypeIsDefined - && !valueTargetTypeIsDefined - && ( nullValueMappingStrategy == null ) ) { - - messager.printMessage( method, Message.MAPMAPPING_NO_ELEMENTS ); - } - - SelectionParameters keySelection = new SelectionParameters( - mapMapping.keyQualifiedBy(), - mapMapping.keyQualifiedByName(), - keyTargetTypeIsDefined ? mapMapping.keyTargetType() : null, - typeUtils - ); - - SelectionParameters valueSelection = new SelectionParameters( - mapMapping.valueQualifiedBy(), - mapMapping.valueQualifiedByName(), - valueTargetTypeIsDefined ? mapMapping.valueTargetType() : null, - typeUtils - ); - - FormattingParameters keyFormatting = new FormattingParameters( - mapMapping.keyDateFormat(), - mapMapping.keyNumberFormat(), - mapMapping.mirror, - mapMapping.values.keyDateFormat(), - method - ); - - FormattingParameters valueFormatting = new FormattingParameters( - mapMapping.valueDateFormat(), - mapMapping.valueNumberFormat(), - mapMapping.mirror, - mapMapping.values.valueDateFormat(), - method - ); - - return new MapMapping( - keyFormatting, - keySelection, - valueFormatting, - valueSelection, - mapMapping.mirror, - nullValueMappingStrategy - ); - } - - private MapMapping(FormattingParameters keyFormatting, SelectionParameters keySelectionParameters, - FormattingParameters valueFormatting, SelectionParameters valueSelectionParameters, AnnotationMirror mirror, - NullValueMappingStrategyPrism nvms ) { - this.keyFormattingParameters = keyFormatting; - this.keySelectionParameters = keySelectionParameters; - this.valueFormattingParameters = valueFormatting; - this.valueSelectionParameters = valueSelectionParameters; - this.mirror = mirror; - this.nullValueMappingStrategy = nvms; - } - - public FormattingParameters getKeyFormattingParameters() { - return keyFormattingParameters; - } - - public SelectionParameters getKeySelectionParameters() { - return keySelectionParameters; - } - - public FormattingParameters getValueFormattingParameters() { - return valueFormattingParameters; - } - - public SelectionParameters getValueSelectionParameters() { - return valueSelectionParameters; - } - - 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..2766faa64c --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperConfigOptions.java @@ -0,0 +1,196 @@ +/* + * Copyright 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.util.ElementUtils; +import org.mapstruct.ap.internal.gem.BuilderGem; +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; + +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 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..21cb7813c4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapperOptions.java @@ -0,0 +1,244 @@ +/* + * Copyright 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.util.ElementUtils; +import org.mapstruct.ap.internal.option.Options; +import org.mapstruct.ap.internal.gem.BuilderGem; +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; + +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 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 f9bd4d4a30..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java +++ /dev/null @@ -1,653 +0,0 @@ -/* - * Copyright 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.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 javax.lang.model.util.Types; - -import org.mapstruct.ap.internal.model.common.FormattingParameters; -import org.mapstruct.ap.internal.model.common.Parameter; -import org.mapstruct.ap.internal.model.common.TypeFactory; -import org.mapstruct.ap.internal.prism.MappingPrism; -import org.mapstruct.ap.internal.prism.MappingsPrism; -import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; -import org.mapstruct.ap.internal.util.AccessorNamingUtils; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.Message; -import org.mapstruct.ap.internal.util.Strings; - -/** - * 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 defaultJavaExpression; - private final String targetName; - private final String defaultValue; - private final FormattingParameters formattingParameters; - private final SelectionParameters selectionParameters; - - 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 final NullValueCheckStrategyPrism nullValueCheckStrategy; - private final NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy; - - private SourceReference sourceReference; - private TargetReference targetReference; - - public static Map> fromMappingsPrism(MappingsPrism mappingsAnnotation, - ExecutableElement method, FormattingMessager messager, Types typeUtils) { - Map> mappings = new HashMap<>(); - - for ( MappingPrism mappingPrism : mappingsAnnotation.value() ) { - Mapping mapping = fromMappingPrism( mappingPrism, method, messager, typeUtils ); - 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, Types typeUtils) { - - if (!isConsistent( mappingPrism, element, messager ) ) { - 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 defaultExpression = getDefaultExpression( mappingPrism, element, messager ); - String dateFormat = mappingPrism.values.dateFormat() == null ? null : mappingPrism.dateFormat(); - String numberFormat = mappingPrism.values.numberFormat() == null ? null : mappingPrism.numberFormat(); - String defaultValue = mappingPrism.values.defaultValue() == null ? null : mappingPrism.defaultValue(); - - boolean resultTypeIsDefined = mappingPrism.values.resultType() != null; - List dependsOn = - mappingPrism.dependsOn() != null ? mappingPrism.dependsOn() : Collections.emptyList(); - - FormattingParameters formattingParam = new FormattingParameters( - dateFormat, - numberFormat, - mappingPrism.mirror, - mappingPrism.values.dateFormat(), - element - ); - SelectionParameters selectionParams = new SelectionParameters( - mappingPrism.qualifiedBy(), - mappingPrism.qualifiedByName(), - resultTypeIsDefined ? mappingPrism.resultType() : null, - typeUtils - ); - - NullValueCheckStrategyPrism nullValueCheckStrategy = - null == mappingPrism.values.nullValueCheckStrategy() - ? null - : NullValueCheckStrategyPrism.valueOf( mappingPrism.nullValueCheckStrategy() ); - - NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy = - null == mappingPrism.values.nullValuePropertyMappingStrategy() - ? null - : NullValuePropertyMappingStrategyPrism.valueOf( mappingPrism.nullValuePropertyMappingStrategy() ); - - return new Mapping( - source, - constant, - expression, - defaultExpression, - mappingPrism.target(), - defaultValue, - mappingPrism.ignore(), - mappingPrism.mirror, - mappingPrism.values.source(), - mappingPrism.values.target(), - formattingParam, - selectionParams, - mappingPrism.values.dependsOn(), - dependsOn, - nullValueCheckStrategy, - nullValuePropertyMappingStrategy - ); - } - - public static Mapping forIgnore( String targetName) { - return new Mapping( - null, - null, - null, - null, - targetName, - null, - true, - null, - null, - null, - null, - null, - null, - new ArrayList(), - null, - null - ); - } - - private static boolean isConsistent(MappingPrism mappingPrism, ExecutableElement element, - FormattingMessager messager) { - - if ( mappingPrism.target().isEmpty() ) { - messager.printMessage( - element, - mappingPrism.mirror, - mappingPrism.values.target(), - Message.PROPERTYMAPPING_EMPTY_TARGET - ); - return false; - } - - if ( !mappingPrism.source().isEmpty() && mappingPrism.values.constant() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_SOURCE_AND_CONSTANT_BOTH_DEFINED - ); - return false; - } - else if ( !mappingPrism.source().isEmpty() && mappingPrism.values.expression() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_SOURCE_AND_EXPRESSION_BOTH_DEFINED - ); - return false; - } - else if ( mappingPrism.values.expression() != null && mappingPrism.values.constant() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_EXPRESSION_AND_CONSTANT_BOTH_DEFINED - ); - return false; - } - else if ( mappingPrism.values.expression() != null && mappingPrism.values.defaultValue() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_VALUE_BOTH_DEFINED - ); - return false; - } - else if ( mappingPrism.values.constant() != null && mappingPrism.values.defaultValue() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_VALUE_BOTH_DEFINED - ); - return false; - } - else if ( mappingPrism.values.expression() != null && mappingPrism.values.defaultExpression() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_EXPRESSION_AND_DEFAULT_EXPRESSION_BOTH_DEFINED - ); - return false; - } - else if ( mappingPrism.values.constant() != null && mappingPrism.values.defaultExpression() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_CONSTANT_AND_DEFAULT_EXPRESSION_BOTH_DEFINED - ); - return false; - } - else if ( mappingPrism.values.defaultValue() != null && mappingPrism.values.defaultExpression() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_DEFAULT_VALUE_AND_DEFAULT_EXPRESSION_BOTH_DEFINED - ); - return false; - } - else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null - && mappingPrism.values.defaultValue() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_DEFAULT_VALUE_AND_NVPMS - ); - return false; - } - else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null - && mappingPrism.values.constant() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_CONSTANT_VALUE_AND_NVPMS - ); - return false; - } - else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null - && mappingPrism.values.expression() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_EXPRESSION_VALUE_AND_NVPMS - ); - return false; - } - else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null - && mappingPrism.values.defaultExpression() != null ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_DEFAULT_EXPERSSION_AND_NVPMS - ); - return false; - } - else if ( mappingPrism.values.nullValuePropertyMappingStrategy() != null - && mappingPrism.ignore() != null && mappingPrism.ignore() ) { - messager.printMessage( - element, - mappingPrism.mirror, - Message.PROPERTYMAPPING_IGNORE_AND_NVPMS - ); - return false; - } - return true; - } - - @SuppressWarnings("checkstyle:parameternumber") - private Mapping( String sourceName, String constant, String javaExpression, String defaultJavaExpression, - String targetName, String defaultValue, boolean isIgnored, AnnotationMirror mirror, - AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue, - FormattingParameters formattingParameters, SelectionParameters selectionParameters, - AnnotationValue dependsOnAnnotationValue, List dependsOn, - NullValueCheckStrategyPrism nullValueCheckStrategy, - NullValuePropertyMappingStrategyPrism nullValuePropertyMappingStrategy ) { - this.sourceName = sourceName; - this.constant = constant; - this.javaExpression = javaExpression; - this.defaultJavaExpression = defaultJavaExpression; - this.targetName = targetName; - this.defaultValue = defaultValue; - this.isIgnored = isIgnored; - this.mirror = mirror; - this.sourceAnnotationValue = sourceAnnotationValue; - this.targetAnnotationValue = targetAnnotationValue; - this.formattingParameters = formattingParameters; - this.selectionParameters = selectionParameters; - this.dependsOnAnnotationValue = dependsOnAnnotationValue; - this.dependsOn = dependsOn; - this.nullValueCheckStrategy = nullValueCheckStrategy; - this.nullValuePropertyMappingStrategy = nullValuePropertyMappingStrategy; - } - - private Mapping( Mapping mapping, TargetReference targetReference ) { - this.sourceName = mapping.sourceName; - this.constant = mapping.constant; - this.javaExpression = mapping.javaExpression; - this.defaultJavaExpression = mapping.defaultJavaExpression; - this.targetName = Strings.join( targetReference.getElementNames(), "." ); - this.defaultValue = mapping.defaultValue; - this.isIgnored = mapping.isIgnored; - this.mirror = mapping.mirror; - this.sourceAnnotationValue = mapping.sourceAnnotationValue; - this.targetAnnotationValue = mapping.targetAnnotationValue; - this.formattingParameters = mapping.formattingParameters; - this.selectionParameters = mapping.selectionParameters; - this.dependsOnAnnotationValue = mapping.dependsOnAnnotationValue; - this.dependsOn = mapping.dependsOn; - this.sourceReference = mapping.sourceReference; - this.targetReference = targetReference; - this.nullValueCheckStrategy = mapping.nullValueCheckStrategy; - this.nullValuePropertyMappingStrategy = mapping.nullValuePropertyMappingStrategy; - } - - private Mapping( Mapping mapping, SourceReference sourceReference ) { - this.sourceName = Strings.join( sourceReference.getElementNames(), "." ); - this.constant = mapping.constant; - this.javaExpression = mapping.javaExpression; - this.defaultJavaExpression = mapping.defaultJavaExpression; - this.targetName = mapping.targetName; - this.defaultValue = mapping.defaultValue; - this.isIgnored = mapping.isIgnored; - this.mirror = mapping.mirror; - this.sourceAnnotationValue = mapping.sourceAnnotationValue; - this.targetAnnotationValue = mapping.targetAnnotationValue; - this.formattingParameters = mapping.formattingParameters; - this.selectionParameters = mapping.selectionParameters; - this.dependsOnAnnotationValue = mapping.dependsOnAnnotationValue; - this.dependsOn = mapping.dependsOn; - this.sourceReference = sourceReference; - this.targetReference = mapping.targetReference; - this.nullValueCheckStrategy = mapping.nullValueCheckStrategy; - this.nullValuePropertyMappingStrategy = mapping.nullValuePropertyMappingStrategy; - } - - 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 String getDefaultExpression(MappingPrism mappingPrism, ExecutableElement element, - FormattingMessager messager) { - if ( mappingPrism.defaultExpression().isEmpty() ) { - return null; - } - - Matcher javaExpressionMatcher = JAVA_EXPRESSION.matcher( mappingPrism.defaultExpression() ); - - if ( !javaExpressionMatcher.matches() ) { - messager.printMessage( - element, mappingPrism.mirror, mappingPrism.values.defaultExpression(), - Message.PROPERTYMAPPING_INVALID_DEFAULT_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, - AccessorNamingUtils accessorNaming) { - init( method, messager, typeFactory, accessorNaming, false, null ); - } - - /** - * Initialize the Mapping. - * - * @param method the source method that the mapping belongs to - * @param messager the messager that can be used for outputting messages - * @param typeFactory the type factory - * @param accessorNaming the accessor naming utils - * @param isReverse whether the init is for a reverse mapping - * @param reverseSourceParameter the source parameter from the revers mapping - */ - private void init(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory, - AccessorNamingUtils accessorNaming, boolean isReverse, - Parameter reverseSourceParameter) { - - if ( !method.isEnumMapping() ) { - sourceReference = new SourceReference.BuilderFromMapping() - .mapping( this ) - .method( method ) - .messager( messager ) - .typeFactory( typeFactory ) - .build(); - - targetReference = new TargetReference.BuilderFromTargetMapping() - .mapping( this ) - .isReverse( isReverse ) - .method( method ) - .messager( messager ) - .typeFactory( typeFactory ) - .accessorNaming( accessorNaming ) - .reverseSourceParameter( reverseSourceParameter ) - .build(); - } - } - - /** - * Initializes the mapping with a new source parameter. - * - * @param sourceParameter sets the source parameter that acts as a basis for this mapping - */ - public void init( Parameter sourceParameter ) { - if ( sourceReference != null ) { - SourceReference oldSourceReference = sourceReference; - sourceReference = new SourceReference.BuilderFromSourceReference() - .sourceParameter( sourceParameter ) - .sourceReference( oldSourceReference ) - .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 getDefaultJavaExpression() { - return defaultJavaExpression; - } - - public String getTargetName() { - return targetName; - } - - public String getDefaultValue() { - return defaultValue; - } - - public FormattingParameters getFormattingParameters() { - return formattingParameters; - } - - public SelectionParameters getSelectionParameters() { - return selectionParameters; - } - - 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 TargetReference getTargetReference() { - return targetReference; - } - - public NullValueCheckStrategyPrism getNullValueCheckStrategy() { - return nullValueCheckStrategy; - } - - public NullValuePropertyMappingStrategyPrism getNullValuePropertyMappingStrategy() { - return nullValuePropertyMappingStrategy; - } - - public Mapping popTargetReference() { - if ( targetReference != null ) { - TargetReference newTargetReference = targetReference.pop(); - if (newTargetReference != null ) { - return new Mapping(this, newTargetReference ); - } - } - return null; - } - - public Mapping popSourceReference() { - if ( sourceReference != null ) { - SourceReference newSourceReference = sourceReference.pop(); - if (newSourceReference != null ) { - return new Mapping(this, newSourceReference ); - } - } - return null; - } - - public List getDependsOn() { - return dependsOn; - } - - public Mapping reverse(SourceMethod method, FormattingMessager messager, TypeFactory typeFactory, - AccessorNamingUtils accessorNaming) { - - // mapping can only be reversed if the source was not a constant nor an expression nor a nested property - // and the mapping is not a 'target-source-ignore' mapping - if ( constant != null || javaExpression != null || ( isIgnored && sourceName == null ) ) { - return null; - } - - Mapping reverse = new Mapping( - sourceName != null ? targetName : null, - null, // constant - null, // expression - null, // defaultExpression - sourceName != null ? sourceName : targetName, - null, - isIgnored, - mirror, - sourceAnnotationValue, - targetAnnotationValue, - formattingParameters, - selectionParameters, - dependsOnAnnotationValue, - Collections.emptyList(), - nullValueCheckStrategy, - nullValuePropertyMappingStrategy - ); - - reverse.init( - method, - messager, - typeFactory, - accessorNaming, - true, - sourceReference != null ? sourceReference.getParameter() : null - ); - - // check if the reverse mapping has indeed a write accessor, otherwise the mapping cannot be reversed - if ( !reverse.targetReference.isValid() ) { - return null; - } - - 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, - defaultJavaExpression, - targetName, - defaultValue, - isIgnored, - mirror, - sourceAnnotationValue, - targetAnnotationValue, - formattingParameters, - selectionParameters, - dependsOnAnnotationValue, - dependsOn, - nullValueCheckStrategy, - nullValuePropertyMappingStrategy - ); - - if ( sourceReference != null ) { - mapping.sourceReference = sourceReference.copyForInheritanceTo( method ); - } - - // TODO... must something be done here? Andreas? - mapping.targetReference = targetReference; - - 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..ab2957086f --- /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.util.ElementUtils; + +import org.mapstruct.ap.internal.gem.MappingControlGem; +import org.mapstruct.ap.internal.gem.MappingControlUseGem; +import org.mapstruct.ap.internal.gem.MappingControlsGem; + +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..08ac1a683a --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MappingMethodOptions.java @@ -0,0 +1,414 @@ +/* + * Copyright 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 static org.mapstruct.ap.internal.model.source.MappingOptions.getMappingTargetNamesBy; + +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; + +/** + * 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 index a471e1c6af..7d0ab3a7ec 100644 --- 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 @@ -5,9 +5,13 @@ */ 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 { @@ -18,18 +22,34 @@ public final class MappingMethodUtils { private MappingMethodUtils() { } - /** * Checks if the provided {@code method} is for enum mapping. A Method is an Enum Mapping method when the - * source parameter and result type are enum types. + *
        + *
      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) { - return method.getSourceParameters().size() == 1 - && first( method.getSourceParameters() ).getType().isEnumType() - && method.getResultType().isEnumType(); + 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 3d5bf302e1..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 @@ -5,338 +5,582 @@ */ 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.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; 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.prism.CollectionMappingStrategyPrism; -import org.mapstruct.ap.internal.util.AccessorNamingUtils; +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.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.accessor.Accessor; +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 static final MappingOptions EMPTY = new MappingOptions( Collections.>emptyMap(), - null, - null, - null, - Collections.emptyList(), - false - ); - private Map> mappings; - private IterableMapping iterableMapping; - private MapMapping mapMapping; - private BeanMapping beanMapping; - private List valueMappings; - private boolean fullyInitialized; - private final boolean restrictToDefinedMappings; - - public MappingOptions(Map> mappings, IterableMapping iterableMapping, MapMapping mapMapping, - BeanMapping beanMapping, List valueMappings, boolean restrictToDefinedMappings ) { - this.mappings = mappings; - this.iterableMapping = iterableMapping; - this.mapMapping = mapMapping; - this.beanMapping = beanMapping; - this.valueMappings = valueMappings; - this.restrictToDefinedMappings = restrictToDefinedMappings; +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 { + + 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; + } } - /** - * creates empty mapping options - * - * @return empty mapping options - */ - public static MappingOptions empty() { - return EMPTY; + public static Set getMappingTargetNamesBy(Predicate predicate, + Set mappings) { + return mappings.stream() + .filter( predicate ) + .map( MappingOptions::getTargetName ) + .collect( Collectors.toCollection( LinkedHashSet::new ) ); } - /** - * creates mapping options with only regular mappings - * - * @param mappings regular mappings to add - * @param restrictToDefinedMappings whether to restrict the mappings only to the defined mappings - * @return MappingOptions with only regular mappings - */ - public static MappingOptions forMappingsOnly(Map> mappings, - boolean restrictToDefinedMappings) { - return forMappingsOnly( mappings, restrictToDefinedMappings, restrictToDefinedMappings ); + 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 ); + } } - /** - * creates mapping options with only regular mappings - * - * @param mappings regular mappings to add - * @param restrictToDefinedMappings whether to restrict the mappings only to the defined mappings - * @param forForgedMethods whether the mappings are for forged methods - * @return MappingOptions with only regular mappings - */ - public static MappingOptions forMappingsOnly(Map> mappings, - boolean restrictToDefinedMappings, boolean forForgedMethods) { + 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( - mappings, + targetName, + null, + null, + null, + null, + null, + null, + null, + null, + null, + true, + null, + SelectionParameters.empty(), + Collections.emptySet(), null, null, - forForgedMethods ? BeanMapping.forForgedMethods() : null, - Collections.emptyList(), - restrictToDefinedMappings + 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; + } } - /** - * @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. TODO. Remove the value list when 2.0 - */ - public Map> getMappings() { - return mappings; + @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; } - /** - * Check there are nested target references for this mapping options. - * - * @return boolean, true if there are nested target references - */ - public boolean hasNestedTargetReferences() { - for ( List mappingList : mappings.values() ) { - for ( Mapping mapping : mappingList ) { - TargetReference targetReference = mapping.getTargetReference(); - if ( targetReference.isValid() && targetReference.getPropertyEntries().size() > 1 ) { - return true; - } - } + 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 false; + + return javaExpressionMatcher.group( 1 ).trim(); } - /** - * - * @return all dependencies to other properties the contained mappings are dependent on - */ - public List collectNestedDependsOn() { + 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; + } - List nestedDependsOn = new ArrayList<>(); - for ( List mappingList : mappings.values() ) { - for ( Mapping mapping : mappingList ) { - nestedDependsOn.addAll( mapping.getDependsOn() ); - } + 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 nestedDependsOn; + + return javaExpressionMatcher.group( 1 ).trim(); + } + + public String getTargetName() { + return targetName; + } + + public AnnotationValue getTargetAnnotationValue() { + return targetAnnotationValue; } /** - * Initializes the underlying mappings with a new property. Specifically used in in combination with forged methods - * where the new parameter name needs to be established at a later moment. + * Returns the complete source name of this mapping, either a qualified (e.g. {@code parameter1.foo}) or + * unqualified (e.g. {@code foo}) property reference. * - * @param sourceParameter the new source parameter + * @return The complete source name of this mapping. */ - public void initWithParameter( Parameter sourceParameter ) { - for ( List mappingList : mappings.values() ) { - for ( Mapping mapping : mappingList ) { - mapping.init( sourceParameter ); - } - } + 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 IterableMapping getIterableMapping() { - return iterableMapping; + public boolean isIgnored() { + return isIgnored; } - public MapMapping getMapMapping() { - return mapMapping; + public AnnotationMirror getMirror() { + return Optional.ofNullable( mapping ).map( MappingGem::mirror ).orElse( null ); } - public BeanMapping getBeanMapping() { - return beanMapping; + public Element getElement() { + return element; } - public List getValueMappings() { - return valueMappings; + public AnnotationValue getDependsOnAnnotationValue() { + return Optional.ofNullable( mapping ) + .map( MappingGem::dependsOn ) + .map( GemValue::getAnnotationValue ) + .orElse( null ); } - public void setMappings(Map> mappings) { - this.mappings = mappings; + public Set getDependsOn() { + return dependsOn; } - public void setIterableMapping(IterableMapping iterableMapping) { - this.iterableMapping = iterableMapping; + public InheritContext getInheritContext() { + return inheritContext; } - public void setMapMapping(MapMapping mapMapping) { - this.mapMapping = mapMapping; + @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 setBeanMapping(BeanMapping beanMapping) { - this.beanMapping = beanMapping; + @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 setValueMappings(List valueMappings) { - this.valueMappings = valueMappings; + @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 templateMethod the template method for the inheritance + * @param beanMappingOptions the bean mapping options * - * @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 accessorNaming the accessor naming utils + * @return the copy */ - public void applyInheritedOptions(MappingOptions inherited, boolean isInverse, SourceMethod method, - FormattingMessager messager, TypeFactory typeFactory, - AccessorNamingUtils accessorNaming) { - 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( BeanMapping.forInheritance( inherited.getBeanMapping() ) ); - } - } - - if ( getValueMappings() == null ) { - if ( inherited.getValueMappings() != null ) { - // there were no mappings, so the inherited mappings are the new ones - setValueMappings( inherited.getValueMappings() ); - } - else { - setValueMappings( Collections.emptyList() ); - } - } - else { - if ( inherited.getValueMappings() != null ) { - // iff there are also inherited mappings, we reverse and add them. - for ( ValueMapping inheritedValueMapping : inherited.getValueMappings() ) { - ValueMapping valueMapping = isInverse ? inheritedValueMapping.reverse() : inheritedValueMapping; - if ( valueMapping != null - && !getValueMappings().contains( valueMapping ) ) { - getValueMappings().add( valueMapping ); - } - } - } - - } - - Map> newMappings = new HashMap<>(); - - for ( List lmappings : inherited.getMappings().values() ) { - for ( Mapping mapping : lmappings ) { - if ( isInverse ) { - mapping = mapping.reverse( method, messager, typeFactory, accessorNaming ); - } - - 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() ); - - // filter new mappings - filterNestedTargetIgnores( newMappings ); - - setMappings( newMappings ); - } - } - - public void applyIgnoreAll(MappingOptions inherited, SourceMethod method, FormattingMessager messager, - TypeFactory typeFactory, AccessorNamingUtils accessorNaming) { - CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy(); - Type writeType = method.getResultType(); - if ( !method.isUpdateMethod() ) { - writeType = writeType.getEffectiveType(); - } - Map writeAccessors = writeType.getPropertyWriteAccessors( cms ); - List mappedPropertyNames = new ArrayList<>(); - for ( String targetMappingName : mappings.keySet() ) { - mappedPropertyNames.add( targetMappingName.split( "\\." )[0] ); - } - for ( String targetPropertyName : writeAccessors.keySet() ) { - if ( !mappedPropertyNames.contains( targetPropertyName ) ) { - Mapping mapping = Mapping.forIgnore( targetPropertyName ); - mapping.init( method, messager, typeFactory, accessorNaming ); - mappings.put( targetPropertyName, Arrays.asList( mapping ) ); - } - } - } - - private void filterNestedTargetIgnores( Map> mappings) { - - // collect all properties to ignore, and safe their target name ( == same name as first ref target property) - Set ignored = new HashSet<>(); - for ( Map.Entry> mappingEntry : mappings.entrySet() ) { - Mapping mapping = first( mappingEntry.getValue() ); // list only used for deprecated enums mapping - if ( mapping.isIgnored() && mapping.getTargetReference().isValid() ) { - ignored.add( mapping.getTargetName() ); - } - } + 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; + } - // collect all entries to remove (avoid concurrent modification) - Set toBeRemoved = new HashSet<>(); - for ( Map.Entry> mappingEntry : mappings.entrySet() ) { - Mapping mapping = first( mappingEntry.getValue() ); // list only used for deprecated enums mapping - TargetReference targetReference = mapping.getTargetReference(); - if ( targetReference.isValid() - && targetReference.getPropertyEntries().size() > 1 - && ignored.contains( first( targetReference.getPropertyEntries() ).getName() ) ) { - toBeRemoved.add( mappingEntry.getKey() ); - } + @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 ); + } - // finall remove all duplicates - mappings.keySet().removeAll( toBeRemoved ); + @Override + public int hashCode() { + return Objects.hash( targetName ); + } + @Override + public String toString() { + return "Mapping {" + + "\n sourceName='" + sourceName + "\'," + + "\n targetName='" + targetName + "\'," + + "\n}"; } - public boolean isRestrictToDefinedMappings() { - return restrictToDefinedMappings; + @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 46810e0d4d..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 @@ -6,14 +6,12 @@ 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: @@ -85,7 +83,7 @@ public interface Method { Parameter getMappingTargetParameter(); /** - * Returns whether the meethod is designated as bean factory for + * 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. @@ -99,7 +97,6 @@ public interface Method { */ Parameter getTargetTypeParameter(); - /** * Returns the {@link Accessibility} of this method. * @@ -129,7 +126,6 @@ public interface Method { */ Type getResultType(); - /** * * @return the names of the parameters of this mapping method @@ -165,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) */ @@ -186,5 +176,28 @@ public interface Method { * * @return the mapping options for this method */ - MappingOptions getMappingOptions(); + 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 20bec62bf9..3d8bfea135 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 @@ -5,25 +5,18 @@ */ 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 org.mapstruct.ap.internal.util.TypeUtils; /** * SourceMethodMatcher $8.4 of the JavaLanguage specification describes a method body as such: @@ -53,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; @@ -66,353 +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<>(); + boolean matches(List sourceTypes, Type targetType) { - if ( candidateMethod.getParameters().size() == sourceTypes.size() ) { - int i = 0; - for ( Parameter candidateParam : candidateMethod.getParameters() ) { - Type sourceType = sourceTypes.get( i++ ); - if ( sourceType == null - || !matchSourceType( sourceType, candidateParam.getType(), genericTypesMap ) ) { - return false; - } - } - } - else { + GenericAnalyser analyser = + new GenericAnalyser( typeFactory, typeUtils, candidateMethod, sourceTypes, targetType ); + if ( !analyser.lineUp() ) { 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 ) ) { + 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 result type - if ( !matchResultType( resultType, genericTypesMap ) ) { - return false; - } - - // check if all type parameters are indeed mapped - if ( candidateMethod.getExecutable().getTypeParameters().size() != genericTypesMap.size() ) { - 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 { - return false; + this.candidateReturnType = candidateMethod.getReturnType(); } } + else { + this.candidateParTypes = candidateMethod.getParameters().stream() + .map( Parameter::getType ) + .collect( Collectors.toList() ); + this.candidateReturnType = candidateMethod.getReturnType(); + } + 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() ); - } - - private enum Assignability { - VISITED_ASSIGNABLE_FROM, VISITED_ASSIGNABLE_TO; - - Assignability invert() { - return this == VISITED_ASSIGNABLE_FROM - ? VISITED_ASSIGNABLE_TO - : VISITED_ASSIGNABLE_FROM; + /** + * {@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; } - } - private class TypeMatcher extends SimpleTypeVisitor6 { - private final Assignability assignability; - private final Map genericTypesMap; - private final TypeMatcher inverse; + /** + * @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; + } - TypeMatcher(Assignability assignability, Map genericTypesMap) { - super( Boolean.FALSE ); // default value - this.assignability = assignability; - this.genericTypesMap = genericTypesMap; - this.inverse = new TypeMatcher( this, genericTypesMap ); - } + boolean foundAMatch = false; - TypeMatcher(TypeMatcher inverse, Map genericTypesMap) { - super( Boolean.FALSE ); // default value - this.assignability = inverse.assignability.invert(); - this.genericTypesMap = genericTypesMap; - this.inverse = inverse; - } + for ( Type mthdParType : candidateMethod.getTypeParameters() ) { - @Override - public Boolean visitPrimitive(PrimitiveType t, TypeMirror p) { - return typeUtils.isSameType( t, p ); - } + // 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 visitArray(ArrayType t, TypeMirror 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; + } - if ( p.getKind().equals( TypeKind.ARRAY ) ) { - return t.getComponentType().accept( this, ( (ArrayType) p ).getComponentType() ); - } - else { - return Boolean.FALSE; - } - } + foundAMatch = true; // there is a rare case where we do not arrive here at all. - @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 ( rawAssignabilityMatches( t, t1 ) ) { - if ( t.getTypeArguments().size() == t1.getTypeArguments().size() ) { - // compare type var side by side - for ( int i = 0; i < t.getTypeArguments().size(); i++ ) { - if ( !visit( t.getTypeArguments().get( i ), t1.getTypeArguments().get( i ) ) ) { - return Boolean.FALSE; - } - } - return Boolean.TRUE; + // 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 ); + } + + // 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 ); } - else { - // return true (e.g. matching Enumeration with an enumeration E) - // but do not try to line up raw type arguments with types that do have arguments. - return assignability == Assignability.VISITED_ASSIGNABLE_TO ? - !t1.getTypeArguments().isEmpty() : !t.getTypeArguments().isEmpty(); + 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 if ( p.getKind() == TypeKind.WILDCARD ) { - return inverse.visit( p, t ); // inverse, as we switch the params - } - else { - return Boolean.FALSE; - } + return foundAMatch; } - private boolean rawAssignabilityMatches(DeclaredType t1, DeclaredType t2) { - if ( assignability == Assignability.VISITED_ASSIGNABLE_TO ) { - return typeUtils.isAssignable( toRawType( t1 ), toRawType( t2 ) ); - } - else { - return typeUtils.isAssignable( toRawType( t2 ), toRawType( t1 ) ); + /** + * 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.isSubtype( p, p1 ); - } - else { - // check if types are in bound - TypeMirror lowerBound = t.getLowerBound(); - TypeMirror upperBound = t.getUpperBound(); - if ( ( isNullType( lowerBound ) || typeUtils.isSubtype( lowerBound, p ) ) - && ( isNullType( upperBound ) || typeUtils.isSubtype( 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 visit( extendsBound, p ); - - 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; + } + typeArgs[i] = matchingType.getTypeMirror(); + } + else if ( typeFromCandidateMethodTypeParameter.isWildCardBoundByTypeVar() + || typeFromCandidateMethodTypeParameter.isTypeVar() + || typeFromCandidateMethodTypeParameter.isArrayTypeVar() + ) { + Type matchingType = pairs.get( typeFromCandidateMethodTypeParameter ); + 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 ( typeUtils.isSameType( candidateTypeParam.asType(), 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 != null ? tpe.getBounds() : null; - if ( t != null && bounds != null ) { - for ( TypeMirror bound : bounds ) { - if ( !( bound.getKind() == TypeKind.DECLARED && typeUtils.isSubtype( 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 index 28817bc43f..6552e43505 100644 --- 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 @@ -23,7 +23,7 @@ */ public class ParameterProvidedMethods { private static final ParameterProvidedMethods EMPTY = - new ParameterProvidedMethods( Collections.> emptyMap() ); + new ParameterProvidedMethods( Collections.emptyMap() ); private final Map> parameterToProvidedMethods; private final Map methodToProvidingParameter; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java deleted file mode 100644 index 03bfe25c65..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/PropertyEntry.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 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.Arrays; - -import org.mapstruct.ap.internal.model.common.Type; -import org.mapstruct.ap.internal.util.Strings; -import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; - - -/** - * A PropertyEntry contains information on the name, readAccessor (for source), readAccessor and writeAccessor - * (for targets) and return type of a property. - */ -public class PropertyEntry { - - private final String[] fullName; - private final Accessor readAccessor; - private final Accessor writeAccessor; - private final ExecutableElementAccessor presenceChecker; - private final Type type; - - /** - * Constructor used to create {@link TargetReference} property entries from a mapping - * - * @param fullName - * @param readAccessor - * @param writeAccessor - * @param type - */ - private PropertyEntry(String[] fullName, Accessor readAccessor, Accessor writeAccessor, - ExecutableElementAccessor presenceChecker, Type type) { - this.fullName = fullName; - this.readAccessor = readAccessor; - this.writeAccessor = writeAccessor; - this.presenceChecker = presenceChecker; - this.type = type; - } - - /** - * Constructor used to create {@link TargetReference} property entries - * - * @param fullName name of the property (dot separated) - * @param readAccessor its read accessor - * @param writeAccessor its write accessor - * @param type type of the property - * @return the property entry for given parameters. - */ - public static PropertyEntry forTargetReference(String[] fullName, Accessor readAccessor, - Accessor writeAccessor, Type type) { - return new PropertyEntry( fullName, readAccessor, writeAccessor, null, 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, Accessor readAccessor, - ExecutableElementAccessor presenceChecker, Type type) { - return new PropertyEntry( new String[]{name}, readAccessor, null, presenceChecker, type ); - } - - public String getName() { - return fullName[fullName.length - 1]; - } - - public Accessor getReadAccessor() { - return readAccessor; - } - - public Accessor getWriteAccessor() { - return writeAccessor; - } - - public ExecutableElementAccessor getPresenceChecker() { - return presenceChecker; - } - - public Type getType() { - return type; - } - - public String getFullName() { - return Strings.join( Arrays.asList( fullName ), "." ); - } - - public PropertyEntry pop() { - if ( fullName.length > 1 ) { - String[] newFullName = Arrays.copyOfRange( fullName, 1, fullName.length ); - return new PropertyEntry(newFullName, readAccessor, writeAccessor, presenceChecker, type ); - } - else { - return null; - } - } - - @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; - if ( !Arrays.deepEquals( this.fullName, other.fullName ) ) { - return false; - } - return true; - } - - @Override - public String toString() { - return type + " " + Strings.join( Arrays.asList( fullName ), "." ); - } -} 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 index 0600b01565..4706d219cb 100644 --- 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 @@ -7,8 +7,9 @@ import java.util.Collections; import java.util.List; +import java.util.Objects; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; +import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.model.common.SourceRHS; @@ -20,21 +21,76 @@ */ 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 Types typeUtils; + 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, - Types typeUtils) { - this( qualifiers, qualifyingNames, resultType, typeUtils, null ); + 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, TypeMirror resultType, - Types typeUtils, SourceRHS sourceRHS) { + 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; @@ -56,6 +112,21 @@ 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) @@ -97,24 +168,23 @@ public boolean equals(Object obj) { return false; } - if ( !equals( this.qualifyingNames, other.qualifyingNames ) ) { + if ( !Objects.equals( this.qualifyingNames, other.qualifyingNames ) ) { return false; } - if ( !equals( this.sourceRHS, other.sourceRHS ) ) { + if ( !Objects.equals( this.conditionQualifiers, other.conditionQualifiers ) ) { return false; } - return equals( this.resultType, other.resultType ); - } - - private boolean equals(Object object1, Object object2) { - if ( object1 == null ) { - return (object2 == null); + if ( !Objects.equals( this.conditionQualifyingNames, other.conditionQualifyingNames ) ) { + return false; } - else { - return object1.equals( object2 ); + + if ( !Objects.equals( this.sourceRHS, other.sourceRHS ) ) { + return false; } + + return equals( this.resultType, other.resultType ); } private boolean equals(List mirrors1, List mirrors2) { @@ -142,13 +212,32 @@ private boolean equals(TypeMirror mirror1, TypeMirror 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(), + 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 efe38231d0..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 @@ -5,42 +5,40 @@ */ 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.prism.ObjectFactoryPrism; -import org.mapstruct.ap.internal.util.AccessorNamingUtils; 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; @@ -48,31 +46,35 @@ 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 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 isEnumMapping; private Boolean isValueMapping; private Boolean isIterableMapping; private Boolean isMapMapping; private Boolean isStreamMapping; private final boolean hasObjectFactoryAnnotation; + private final boolean verboseLogging; + public static class Builder { private Type declaringMapper = null; @@ -81,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 AccessorNamingUtils accessorNaming = null; - private FormattingMessager messager = null; - private MapperConfiguration mapperConfig = null; + private MapperOptions mapper = null; private List prototypeMethods = Collections.emptyList(); - private List valueMappings; + private List valueMappings; + private EnumMappingOptions enumMappingOptions; private ParameterProvidedMethods contextProvidedMethods; + private Set conditionOptions; + private List typeParameters; + private Set subclassMappings; + + private boolean verboseLogging; + private SubclassValidator subclassValidator; public Builder setDeclaringMapper(Type declaringMapper) { this.declaringMapper = declaringMapper; @@ -119,53 +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 setValueMappings(List valueMappings) { + public Builder setValueMappingOptionss(List valueMappings) { this.valueMappings = valueMappings; return this; } - public Builder setTypeUtils(Types typeUtils) { - this.typeUtils = typeUtils; + public Builder setEnumMappingOptions(EnumMappingOptions enumMappingOptions) { + this.enumMappingOptions = enumMappingOptions; return this; } - public Builder setTypeFactory(TypeFactory typeFactory) { - this.typeFactory = typeFactory; + public Builder setSubclassMappings(Set subclassMappings) { + this.subclassMappings = subclassMappings; return this; } - public Builder setAccessorNaming(AccessorNamingUtils accessorNaming) { - this.accessorNaming = accessorNaming; + public Builder setSubclassValidator(SubclassValidator subclassValidator) { + this.subclassValidator = subclassValidator; return this; } - public Builder setMessager(FormattingMessager messager) { - this.messager = messager; + public Builder setTypeUtils(TypeUtils typeUtils) { + this.typeUtils = typeUtils; return this; } - public Builder setMapperConfiguration(MapperConfiguration mapperConfig) { - this.mapperConfig = mapperConfig; + public Builder setTypeFactory(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + return this; + } + + public Builder setMapper(MapperOptions mapper) { + this.mapper = mapper; return this; } @@ -174,7 +186,7 @@ public Builder setPrototypeMethods(List prototypeMethods) { return this; } - public Builder setDefininingType(Type definingType) { + public Builder setDefiningType(Type definingType) { this.definingType = definingType; return this; } @@ -184,25 +196,54 @@ public Builder setContextProvidedMethods(ParameterProvidedMethods contextProvide return this; } - public SourceMethod build() { + public Builder setConditionOptions(Set conditionOptions) { + this.conditionOptions = conditionOptions; + return this; + } + + public Builder setVerboseLogging(boolean verboseLogging) { + this.verboseLogging = verboseLogging; + return this; + } - MappingOptions mappingOptions = - new MappingOptions( mappings, iterableMapping, mapMapping, beanMapping, valueMappings, false ); + public SourceMethod build() { - SourceMethod sourceMethod = new SourceMethod( this, mappingOptions ); + 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, accessorNaming ); - } - } + 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 ); } } - private SourceMethod(Builder builder, MappingOptions mappingOptions) { + private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions, + ConditionMethodOptions conditionMethodOptions) { this.declaringMapper = builder.declaringMapper; this.executable = builder.executable; this.parameters = builder.parameters; @@ -210,29 +251,36 @@ private SourceMethod(Builder builder, MappingOptions mappingOptions) { this.exceptionTypes = builder.exceptionTypes; this.accessibility = Accessibility.fromModifiers( builder.executable.getModifiers() ); - this.mappingOptions = mappingOptions; + this.mappingMethodOptions = mappingMethodOptions; + this.conditionMethodOptions = conditionMethodOptions; this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); this.contextProvidedMethods = builder.contextProvidedMethods; + this.typeParameters = builder.typeParameters; this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); - this.hasObjectFactoryAnnotation = ObjectFactoryPrism.getInstanceOn( executable ) != null; + this.sourcePropertyNameParameter = Parameter.getSourcePropertyNameParameter( parameters ); + this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters ); + this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null; this.isObjectFactory = determineIfIsObjectFactory(); this.typeUtils = builder.typeUtils; this.typeFactory = builder.typeFactory; - this.config = builder.mapperConfig; this.prototypeMethods = builder.prototypeMethods; this.mapperToImplement = builder.definingType; + + this.verboseLogging = builder.verboseLogging; } private boolean determineIfIsObjectFactory() { boolean hasNoSourceParameters = getSourceParameters().isEmpty(); boolean hasNoMappingTargetParam = getMappingTargetParameter() == null; + boolean hasNoSourcePropertyNameParam = getSourcePropertyNameParameter() == null; + boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null; return !isLifecycleCallbackMethod() && !returnType.isVoid() - && hasNoMappingTargetParam + && hasNoMappingTargetParam && hasNoSourcePropertyNameParam && hasNoTargetPropertyNameParam && ( hasObjectFactoryAnnotation || hasNoSourceParameters ); } @@ -301,31 +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) { + public boolean inverses(SourceMethod method) { return method.getDeclaringMapper() == null && method.isAbstract() && getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1 - && first( getSourceParameters() ).getType().isAssignableTo( method.getResultType() ) + && getMappingSourceType().isAssignableTo( method.getResultType() ) && getResultType().isAssignableTo( 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 canInheritFrom(SourceMethod method) { 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() ); } @@ -345,10 +382,18 @@ public Parameter getTargetTypeParameter() { return targetTypeParameter; } + public Parameter getSourcePropertyNameParameter() { + return sourcePropertyNameParameter; + } + + public Parameter getTargetPropertyNameParameter() { + return targetPropertyNameParameter; + } + public boolean isIterableMapping() { if ( isIterableMapping == null ) { isIterableMapping = getSourceParameters().size() == 1 - && first( getSourceParameters() ).getType().isIterableType() + && getMappingSourceType().isIterableType() && getResultType().isIterableType(); } return isIterableMapping; @@ -357,9 +402,9 @@ && first( getSourceParameters() ).getType().isIterableType() public boolean isStreamMapping() { if ( isStreamMapping == null ) { isStreamMapping = getSourceParameters().size() == 1 - && ( first( getSourceParameters() ).getType().isIterableType() && getResultType().isStreamType() - || first( getSourceParameters() ).getType().isStreamType() && getResultType().isIterableType() - || first( getSourceParameters() ).getType().isStreamType() && getResultType().isStreamType() ); + && ( getMappingSourceType().isIterableType() && getResultType().isStreamType() + || getMappingSourceType().isStreamType() && getResultType().isIterableType() + || getMappingSourceType().isStreamType() && getResultType().isStreamType() ); } return isStreamMapping; } @@ -367,17 +412,20 @@ public boolean isStreamMapping() { public boolean isMapMapping() { if ( isMapMapping == null ) { isMapMapping = getSourceParameters().size() == 1 - && first( getSourceParameters() ).getType().isMapType() + && getMappingSourceType().isMapType() && getResultType().isMapType(); } return isMapMapping; } - public boolean isEnumMapping() { - if ( isEnumMapping == null ) { - isEnumMapping = MappingMethodUtils.isEnumMapping( this ); - } - return isEnumMapping; + /** + * 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 ); } /** @@ -389,15 +437,11 @@ public boolean isEnumMapping() { public boolean isValueMapping() { if ( isValueMapping == null ) { - isValueMapping = isEnumMapping() && mappingOptions.getMappings().isEmpty(); + isValueMapping = isEnumMapping( this ) && mappingMethodOptions.getMappings().isEmpty(); } return isValueMapping; } - private boolean equals(Object o1, Object o2) { - return (o1 == null && o2 == null) || (o1 != null) && o1.equals( o2 ); - } - @Override public String toString() { StringBuilder sb = new StringBuilder( returnType.toString() ); @@ -412,48 +456,6 @@ 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(); - - // 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 ); - } - } - } - } - - return mappingsOfSourceProperty; - } - - public Parameter getSourceParameter(String sourceParameterName) { - for ( Parameter parameter : getSourceParameters() ) { - if ( parameter.getName().equals( sourceParameterName ) ) { - return parameter; - } - } - - return null; - } - public List getApplicablePrototypeMethods() { if ( applicablePrototypeMethods == null ) { applicablePrototypeMethods = new ArrayList<>(); @@ -473,7 +475,7 @@ public List getApplicableReversePrototypeMethods() { applicableReversePrototypeMethods = new ArrayList<>(); for ( SourceMethod prototype : prototypeMethods ) { - if ( reverses( prototype ) ) { + if ( inverses( prototype ) ) { applicableReversePrototypeMethods.add( prototype ); } } @@ -529,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 @@ -544,8 +540,13 @@ public List getThrownTypes() { } @Override - public MappingOptions getMappingOptions() { - return mappingOptions; + public MappingMethodOptions getOptions() { + return mappingMethodOptions; + } + + @Override + public ConditionMethodOptions getConditionOptions() { + return conditionMethodOptions; } @Override @@ -563,11 +564,6 @@ public Type getDefiningType() { return mapperToImplement; } - @Override - public MapperConfiguration getMapperConfiguration() { - return config; - } - @Override public boolean isLifecycleCallbackMethod() { return Executables.isLifecycleCallbackMethod( getExecutable() ); @@ -597,4 +593,23 @@ public boolean isUpdateMethod() { 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 30b80c8b2f..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceReference.java +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright 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 static org.mapstruct.ap.internal.model.source.PropertyEntry.forSourceReference; -import static org.mapstruct.ap.internal.util.Collections.first; -import static org.mapstruct.ap.internal.util.Collections.last; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -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.util.Extractor; -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.Accessor; -import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; - -/** - * 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; - } - - String sourceNameTrimmed = sourceName.trim(); - if ( !sourceName.equals( sourceNameTrimmed ) ) { - messager.printMessage( - method.getExecutable(), - mapping.getMirror(), - mapping.getSourceAnnotationValue(), - 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 an @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; - - String[] propertyNames = segments; - List entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); - foundEntryMatch = ( entries.size() == propertyNames.length ); - - if ( !foundEntryMatch ) { - //Lets see if the expression contains the parameterName, so parameterName.propName1.propName2 - if ( parameter.getName().equals( segments[0] ) ) { - propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); - 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 ); - 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 propery - * needs to match the parameter name to avoid ambiguity - * - * consider: {@code Target map( Source1 source1, Source2 source2 )} - * entries in an @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 = method.getSourceParameter( parameterName ); - if ( parameter == null ) { - reportMappingError( - Message.PROPERTYMAPPING_INVALID_PARAMETER_NAME, - parameterName, - Strings.join( - method.getSourceParameters(), - ", ", - new Extractor() { - @Override - public String apply(Parameter parameter) { - return parameter.getName(); - } - } - ) - ); - } - } - return parameter; - } - - 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(); - } - String mostSimilarWord = Strings.getMostSimilarWord( - propertyNames[notFoundPropertyIndex], - sourceType.getPropertyReadAccessors().keySet() - ); - List elements = new ArrayList<>( - Arrays.asList( propertyNames ).subList( 0, notFoundPropertyIndex ) - ); - elements.add( mostSimilarWord ); - reportMappingError( - Message.PROPERTYMAPPING_INVALID_PROPERTY_NAME, mapping.getSourceName(), - Strings.join( elements, "." ) - ); - } - } - - private List matchWithSourceAccessorTypes(Type type, String[] entryNames) { - List sourceEntries = new ArrayList<>(); - Type newType = type; - for ( String entryName : entryNames ) { - boolean matchFound = false; - Map sourceReadAccessors = newType.getPropertyReadAccessors(); - Map sourcePresenceCheckers = newType.getPropertyPresenceCheckers(); - - for ( Map.Entry getter : sourceReadAccessors.entrySet() ) { - if ( getter.getKey().equals( entryName ) ) { - newType = typeFactory.getReturnType( - (DeclaredType) newType.getTypeMirror(), - getter.getValue() - ); - sourceEntries.add( forSourceReference( entryName, getter.getValue(), - sourcePresenceCheckers.get( entryName ), 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 Accessor readAccessor; - private ExecutableElementAccessor presenceChecker; - private Type type; - private Parameter sourceParameter; - - public BuilderFromProperty name(String name) { - this.name = name; - return this; - } - - public BuilderFromProperty readAccessor(Accessor readAccessor) { - this.readAccessor = readAccessor; - return this; - } - - public BuilderFromProperty presenceChecker(ExecutableElementAccessor 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( 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.propertyEntries, 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; - } - - /** - * 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 ( parameter != null && sourceParam.getType().isAssignableTo( parameter.getType() ) ) { - replacementParamCandidates.add( sourceParam ); - } - } - - Parameter replacement = parameter; - if ( replacementParamCandidates.size() == 1 ) { - replacement = first( replacementParamCandidates ); - } - - return new SourceReference( replacement, propertyEntries, isValid ); - } - - public SourceReference pop() { - if ( propertyEntries.size() > 1 ) { - List newPropertyEntries = - new ArrayList<>( propertyEntries.subList( 1, propertyEntries.size() ) ); - return new SourceReference( parameter, newPropertyEntries, isValid ); - } - else { - return null; - } - } - - @Override - public String toString() { - - if ( propertyEntries.isEmpty() ) { - return String.format( "parameter \"%s %s\"", getParameter().getType(), getParameter().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( getElementNames(), "." ) - ); - } - } - -} 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/TargetReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/TargetReference.java deleted file mode 100644 index 0edfb362d0..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/TargetReference.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright 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.Arrays; -import java.util.List; -import java.util.Set; - -import javax.lang.model.element.AnnotationMirror; -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.prism.CollectionMappingStrategyPrism; -import org.mapstruct.ap.internal.util.AccessorNamingUtils; -import org.mapstruct.ap.internal.util.Executables; -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.Accessor; - -/** - * 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(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 TargetReference { - - private final Parameter parameter; - private final List propertyEntries; - private final boolean isValid; - - - /** - * Builds a {@link TargetReference} from an {@code @Mappping}. - */ - public static class BuilderFromTargetMapping { - - private Mapping mapping; - private SourceMethod method; - private FormattingMessager messager; - private TypeFactory typeFactory; - private AccessorNamingUtils accessorNaming; - private boolean isReverse; - /** - * Needed when we are building from reverse 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 reversed 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 reverse is created. - */ - private Parameter reverseSourceParameter; - /** - * During {@link #getTargetEntries(Type, String[])} an error can occur. However, we are invoking - * that multiple times because the first entry can also be the name of the parameter. Therefore we keep - * the error message until the end when we can report it. - */ - private MappingErrorMessage errorMessage; - - public BuilderFromTargetMapping messager(FormattingMessager messager) { - this.messager = messager; - return this; - } - - public BuilderFromTargetMapping mapping(Mapping mapping) { - this.mapping = mapping; - return this; - } - - public BuilderFromTargetMapping method(SourceMethod method) { - this.method = method; - return this; - } - - public BuilderFromTargetMapping typeFactory(TypeFactory typeFactory) { - this.typeFactory = typeFactory; - return this; - } - - public BuilderFromTargetMapping accessorNaming(AccessorNamingUtils accessorNaming) { - this.accessorNaming = accessorNaming; - return this; - } - - public BuilderFromTargetMapping isReverse(boolean isReverse) { - this.isReverse = isReverse; - return this; - } - - public BuilderFromTargetMapping reverseSourceParameter(Parameter reverseSourceParameter) { - this.reverseSourceParameter = reverseSourceParameter; - return this; - } - - public TargetReference build() { - - String targetName = mapping.getTargetName(); - - if ( targetName == null ) { - return null; - } - - String targetNameTrimmed = targetName.trim(); - if ( !targetName.equals( targetNameTrimmed ) ) { - messager.printMessage( - method.getExecutable(), - mapping.getMirror(), - mapping.getTargetAnnotationValue(), - Message.PROPERTYMAPPING_WHITESPACE_TRIMMED, - targetName, - targetNameTrimmed - ); - } - String[] segments = targetNameTrimmed.split( "\\." ); - Parameter parameter = method.getMappingTargetParameter(); - - boolean foundEntryMatch; - Type resultType = method.getResultType(); - resultType = typeBasedOnMethod( resultType ); - - // there can be 4 situations - // 1. Return type - // 2. A reverse target reference where the source parameter name is used in the original mapping - // 3. @MappingTarget, with - // 4. or without parameter name. - String[] targetPropertyNames = segments; - List entries = getTargetEntries( resultType, targetPropertyNames ); - foundEntryMatch = (entries.size() == targetPropertyNames.length); - if ( !foundEntryMatch && segments.length > 1 - && matchesSourceOrTargetParameter( segments[0], parameter, reverseSourceParameter, isReverse ) ) { - targetPropertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = getTargetEntries( resultType, targetPropertyNames ); - foundEntryMatch = (entries.size() == targetPropertyNames.length); - } - - if ( !foundEntryMatch && errorMessage != null && !isReverse ) { - // This is called only for reporting errors - errorMessage.report( ); - } - - // foundEntryMatch = isValid, errors are handled here, and the BeanMapping uses that to ignore - // the creation of mapping for invalid TargetReferences - return new TargetReference( parameter, entries, foundEntryMatch ); - } - - private List getTargetEntries(Type type, String[] entryNames) { - - // initialize - CollectionMappingStrategyPrism cms = method.getMapperConfiguration().getCollectionMappingStrategy(); - List targetEntries = new ArrayList<>(); - Type nextType = type; - - // iterate, establish for each entry the target write accessors. Other than setter is only allowed for - // last entry - for ( int i = 0; i < entryNames.length; i++ ) { - - Type mappingType = typeBasedOnMethod( nextType ); - Accessor targetReadAccessor = mappingType.getPropertyReadAccessors().get( entryNames[i] ); - Accessor targetWriteAccessor = mappingType.getPropertyWriteAccessors( cms ).get( entryNames[i] ); - boolean isLast = i == entryNames.length - 1; - boolean isNotLast = i < entryNames.length - 1; - if ( isWriteAccessorNotValidWhenNotLast( targetWriteAccessor, isNotLast ) - || isWriteAccessorNotValidWhenLast( targetWriteAccessor, targetReadAccessor, mapping, isLast ) ) { - // there should always be a write accessor (except for the last when the mapping is ignored and - // there is a read accessor) and there should be read accessor mandatory for all but the last - setErrorMessage( targetWriteAccessor, targetReadAccessor, entryNames, i, nextType ); - break; - } - - if ( isLast || ( accessorNaming.isSetterMethod( targetWriteAccessor ) - || Executables.isFieldAccessor( targetWriteAccessor ) ) ) { - // only intermediate nested properties when they are a true setter or field accessor - // the last may be other readAccessor (setter / getter / adder). - - nextType = findNextType( nextType, targetWriteAccessor, targetReadAccessor ); - - // check if an entry alread exists, otherwise create - String[] fullName = Arrays.copyOfRange( entryNames, 0, i + 1 ); - PropertyEntry propertyEntry = PropertyEntry.forTargetReference( fullName, targetReadAccessor, - targetWriteAccessor, nextType ); - targetEntries.add( propertyEntry ); - } - - } - - return targetEntries; - } - - /** - * Finds the next type based on the initial type. - * - * @param initial for which a next type should be found - * @param targetWriteAccessor the write accessor - * @param targetReadAccessor the read accessor - * @return the next type that should be used for finding a property entry - */ - private Type findNextType(Type initial, Accessor targetWriteAccessor, Accessor targetReadAccessor) { - Type nextType; - Accessor toUse = targetWriteAccessor != null ? targetWriteAccessor : targetReadAccessor; - if ( accessorNaming.isGetterMethod( toUse ) || - Executables.isFieldAccessor( toUse ) ) { - nextType = typeFactory.getReturnType( - (DeclaredType) typeBasedOnMethod( initial ).getTypeMirror(), - toUse - ); - } - else { - nextType = typeFactory.getSingleParameter( - (DeclaredType) typeBasedOnMethod( initial ).getTypeMirror(), - toUse - ).getType(); - } - return nextType; - } - - private void setErrorMessage(Accessor targetWriteAccessor, Accessor targetReadAccessor, String[] entryNames, - int index, Type nextType) { - if ( targetWriteAccessor == null && targetReadAccessor == null ) { - errorMessage = new NoPropertyErrorMessage( mapping, method, messager, entryNames, index, nextType ); - } - else if ( targetWriteAccessor == null ) { - errorMessage = new NoWriteAccessorErrorMessage( mapping, method, messager ); - } - else { - //TODO there is no read accessor. What should we do here? - errorMessage = new NoPropertyErrorMessage( mapping, method, messager, entryNames, index, nextType ); - } - } - - /** - * When we are in an update method, i.e. source parameter with {@code @MappingTarget} then the type should - * be itself, otherwise, we always get the effective type. The reason is that when doing updates we always - * search for setters and getters within the updating type. - */ - private Type typeBasedOnMethod(Type type) { - if ( method.isUpdateMethod() ) { - return type; - } - else { - return type.getEffectiveType(); - } - } - - /** - * A write accessor is not valid if it is {@code null} and it is not last. i.e. for nested target mappings - * there must be a write accessor for all entries except the last one. - * - * @param accessor that needs to be checked - * @param isNotLast whether or not this is the last write accessor in the entry chain - * - * @return {@code true} if the accessor is not valid, {@code false} otherwise - */ - private static boolean isWriteAccessorNotValidWhenNotLast(Accessor accessor, boolean isNotLast) { - return accessor == null && isNotLast; - } - - /** - * For a last accessor to be valid, a read accessor should exist and the mapping should be ignored. All other - * cases represent an invalid write accessor. This method will evaluate to {@code true} if the following is - * {@code true}: - *
        - *
      • {@code writeAccessor} is {@code null}
      • - *
      • It is for the last entry
      • - *
      • A read accessor does not exist, or the mapping is not ignored
      • - *
      - * - * @param writeAccessor that needs to be checked - * @param readAccessor that is used - * @param mapping that is used - * @param isLast whether or not this is the last write accessor in the entry chain - * - * @return {@code true} if the write accessor is not valid, {@code false} otherwise. See description for more - * information - */ - private static boolean isWriteAccessorNotValidWhenLast(Accessor writeAccessor, Accessor readAccessor, - Mapping mapping, boolean isLast) { - return writeAccessor == null && isLast && ( readAccessor == null || !mapping.isIgnored() ); - } - - /** - * Validates that the {@code segment} is the same as the {@code targetParameter} or the {@code - * reverseSourceParameter} names - * - * @param segment that needs to be checked - * @param targetParameter the target parameter if it exists - * @param reverseSourceParameter the reverse source parameter if it exists - * @param isReverse whether a reverse {@link TargetReference} is being built - * - * @return {@code true} if the segment matches the name of the {@code targetParameter} or the name of the - * {@code reverseSourceParameter} when this is a reverse {@link TargetReference} is being built, {@code - * false} otherwise - */ - private static boolean matchesSourceOrTargetParameter(String segment, Parameter targetParameter, - Parameter reverseSourceParameter, boolean isReverse) { - boolean matchesTargetParameter = - targetParameter != null && targetParameter.getName().equals( segment ); - return matchesTargetParameter - || isReverse && reverseSourceParameter != null && reverseSourceParameter.getName().equals( segment ); - } - } - - private TargetReference(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; - } - - public TargetReference pop() { - if ( propertyEntries.size() > 1 ) { - List newPropertyEntries = new ArrayList<>( propertyEntries.size() - 1 ); - for ( PropertyEntry propertyEntry : propertyEntries ) { - PropertyEntry newPropertyEntry = propertyEntry.pop(); - if ( newPropertyEntry != null ) { - newPropertyEntries.add( newPropertyEntry ); - } - } - return new TargetReference( null, newPropertyEntries, isValid ); - } - else { - return null; - } - } - - private abstract static class MappingErrorMessage { - private final Mapping mapping; - private final SourceMethod method; - private final FormattingMessager messager; - - private MappingErrorMessage(Mapping mapping, SourceMethod method, FormattingMessager messager) { - this.mapping = mapping; - this.method = method; - this.messager = messager; - } - - abstract void report(); - - protected void printErrorMessage(Message message, Object... args) { - Object[] errorArgs = new Object[args.length + 2]; - errorArgs[0] = mapping.getTargetName(); - errorArgs[1] = method.getResultType(); - System.arraycopy( args, 0, errorArgs, 2, args.length ); - AnnotationMirror annotationMirror = mapping.getMirror(); - messager.printMessage( method.getExecutable(), annotationMirror, mapping.getSourceAnnotationValue(), - message, errorArgs - ); - } - } - - private static class NoWriteAccessorErrorMessage extends MappingErrorMessage { - - private NoWriteAccessorErrorMessage(Mapping mapping, SourceMethod method, FormattingMessager messager) { - super( mapping, method, messager ); - } - - @Override - public void report() { - printErrorMessage( Message.BEANMAPPING_PROPERTY_HAS_NO_WRITE_ACCESSOR_IN_RESULTTYPE ); - } - } - - private static class NoPropertyErrorMessage extends MappingErrorMessage { - - private final String[] entryNames; - private final int index; - private final Type nextType; - - private NoPropertyErrorMessage(Mapping mapping, SourceMethod method, FormattingMessager messager, - String[] entryNames, int index, Type nextType) { - super( mapping, method, messager ); - this.entryNames = entryNames; - this.index = index; - this.nextType = nextType; - } - - @Override - public void report() { - - Set readAccessors = nextType.getPropertyReadAccessors().keySet(); - String mostSimilarProperty = Strings.getMostSimilarWord( entryNames[index], readAccessors ); - - List elements = new ArrayList<>( Arrays.asList( entryNames ).subList( 0, index ) ); - elements.add( mostSimilarProperty ); - - printErrorMessage( Message.BEANMAPPING_UNKNOWN_PROPERTY_IN_RESULTTYPE, Strings.join( elements, "." ) ); - } - } - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMapping.java deleted file mode 100644 index 7ad385be66..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ValueMapping.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 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.List; - -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.ExecutableElement; - -import org.mapstruct.ap.internal.prism.MappingConstantsPrism; -import org.mapstruct.ap.internal.prism.ValueMappingPrism; -import org.mapstruct.ap.internal.prism.ValueMappingsPrism; -import org.mapstruct.ap.internal.util.FormattingMessager; -import org.mapstruct.ap.internal.util.Message; - -/** - * Represents the mapping between one value constant and another. - * - * @author Sjaak Derksen - */ -public class ValueMapping { - - private final String source; - private final String target; - private final AnnotationMirror mirror; - private final AnnotationValue sourceAnnotationValue; - private final AnnotationValue targetAnnotationValue; - - public static void fromMappingsPrism(ValueMappingsPrism mappingsAnnotation, ExecutableElement method, - FormattingMessager messager, List mappings) { - - boolean anyFound = false; - for ( ValueMappingPrism mappingPrism : mappingsAnnotation.value() ) { - ValueMapping mapping = fromMappingPrism( mappingPrism, method, messager ); - if ( mapping != null ) { - - if ( !mappings.contains( mapping ) ) { - mappings.add( mapping ); - } - else { - messager.printMessage( - method, - mappingPrism.mirror, - mappingPrism.values.target(), - Message.VALUEMAPPING_DUPLICATE_SOURCE, - mappingPrism.source() - ); - } - if ( MappingConstantsPrism.ANY_REMAINING.equals( mapping.source ) - || MappingConstantsPrism.ANY_UNMAPPED.equals( mapping.source ) ) { - if ( anyFound ) { - messager.printMessage( - method, - mappingPrism.mirror, - mappingPrism.values.target(), - Message.VALUEMAPPING_ANY_AREADY_DEFINED, - mappingPrism.source() - ); - } - anyFound = true; - } - } - } - } - - public static ValueMapping fromMappingPrism(ValueMappingPrism mappingPrism, ExecutableElement element, - FormattingMessager messager) { - - return new ValueMapping( mappingPrism.source(), mappingPrism.target(), mappingPrism.mirror, - mappingPrism.values.source(), mappingPrism.values.target() ); - } - - private ValueMapping(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 ValueMapping reverse() { - ValueMapping result; - if ( !MappingConstantsPrism.ANY_REMAINING.equals( source ) - || !MappingConstantsPrism.ANY_UNMAPPED.equals( source ) ) { - result = new ValueMapping( - 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 ValueMapping other = (ValueMapping) obj; - if ( (this.source == null) ? (other.source != null) : !this.source.equals( other.source ) ) { - return false; - } - 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 index 691d93d0f3..df9c8e0b0c 100644 --- 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 @@ -5,18 +5,20 @@ */ package org.mapstruct.ap.internal.model.source.builtin; +import static org.mapstruct.ap.internal.util.Collections.asSet; + 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.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.Strings; - -import static org.mapstruct.ap.internal.util.Collections.asSet; +import org.mapstruct.ap.internal.util.XmlConstants; /** + * Base class for built-in methods for converting from a particular type to {@code XMLGregorianCalendar}. + * * @author Sjaak Derksen */ public abstract class AbstractToXmlGregorianCalendar extends BuiltInMethod { @@ -26,12 +28,12 @@ public abstract class AbstractToXmlGregorianCalendar extends BuiltInMethod { private final Type dataTypeFactoryType; public AbstractToXmlGregorianCalendar(TypeFactory typeFactory) { - this.returnType = typeFactory.getType( XMLGregorianCalendar.class ); - this.dataTypeFactoryType = typeFactory.getType( DatatypeFactory.class ); + 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( DatatypeConfigurationException.class ) + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONFIGURATION_EXCEPTION ) ); } @@ -46,12 +48,12 @@ public Type getReturnType() { } @Override - public BuiltInFieldReference getFieldReference() { - return new FinalField( dataTypeFactoryType, Strings.decapitalize( DatatypeFactory.class.getSimpleName() ) ); + public FieldReference getFieldReference() { + return new FinalField( dataTypeFactoryType, "datatypeFactory" ); } @Override - public BuiltInConstructorFragment getConstructorFragment() { + public ConstructorFragment getConstructorFragment() { return new NewDatatypeFactoryConstructorFragment( ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInConstructorFragment.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInConstructorFragment.java deleted file mode 100644 index ae8dbcc7cd..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInConstructorFragment.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 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; - -/** - * ConstructorFragments are 'code snippets' added to the constructor to initialize fields used by {@link BuiltInMethod} - */ -public interface BuiltInConstructorFragment { -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInFieldReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInFieldReference.java deleted file mode 100644 index 0eb15f289b..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInFieldReference.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 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.Type; - -/** - * reference used by BuiltInMethod to create an additional field in the mapper. - */ -public interface BuiltInFieldReference { - - /** - * - * @return variable name of the field - */ - String getVariableName(); - - /** - * - * @return type of the field - */ - Type getType(); - -} 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 5332651ab0..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 @@ -8,6 +8,7 @@ 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.JaxbConstants; import org.mapstruct.ap.internal.util.JodaTimeConstants; @@ -24,7 +25,7 @@ public class BuiltInMappingMethods { public BuiltInMappingMethods(TypeFactory typeFactory) { boolean isXmlGregorianCalendarPresent = isXmlGregorianCalendarAvailable( typeFactory ); - builtInMethods = new ArrayList<>( 20 ); + builtInMethods = new ArrayList<>( 21 ); if ( isXmlGregorianCalendarPresent ) { builtInMethods.add( new DateToXmlGregorianCalendar( typeFactory ) ); builtInMethods.add( new XmlGregorianCalendarToDate( typeFactory ) ); @@ -33,18 +34,24 @@ public BuiltInMappingMethods(TypeFactory 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 ( isJaxbAvailable( typeFactory ) ) { - builtInMethods.add( new JaxbElemToValue( typeFactory ) ); + if ( isJavaxJaxbAvailable( typeFactory ) ) { + Type type = typeFactory.getType( JaxbConstants.JAVAX_JAXB_ELEMENT_FQN ); + builtInMethods.add( new JaxbElemToValue( type ) ); + } + + if ( isJakartaJaxbAvailable( typeFactory ) ) { + Type type = typeFactory.getType( JaxbConstants.JAKARTA_JAXB_ELEMENT_FQN ); + builtInMethods.add( new JaxbElemToValue( type ) ); } builtInMethods.add( new ZonedDateTimeToCalendar( typeFactory ) ); builtInMethods.add( new CalendarToZonedDateTime( typeFactory ) ); - if ( isXmlGregorianCalendarPresent ) { - builtInMethods.add( new XmlGregorianCalendarToLocalDate( typeFactory ) ); - builtInMethods.add( new LocalDateToXmlGregorianCalendar( typeFactory ) ); - } if ( isJodaTimeAvailable( typeFactory ) && isXmlGregorianCalendarPresent ) { builtInMethods.add( new JodaDateTimeToXmlGregorianCalendar( typeFactory ) ); @@ -58,13 +65,16 @@ public BuiltInMappingMethods(TypeFactory typeFactory) { } } - private static boolean isJaxbAvailable(TypeFactory typeFactory) { - return JaxbConstants.isJaxbElementPresent() && 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 XmlConstants.isXmlGregorianCalendarPresent() && - typeFactory.isTypeAvailable( XmlConstants.JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR ); + return typeFactory.isTypeAvailable( XmlConstants.JAVAX_XML_XML_GREGORIAN_CALENDAR ); } private static boolean isJodaTimeAvailable(TypeFactory typeFactory) { 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 783ea43a5b..6d41872159 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 @@ -5,25 +5,27 @@ */ 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.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.MappingOptions; +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.MapperConfiguration; 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 simple conversation doesn't suffice, e.g. as several lines of source code or a @@ -51,7 +53,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(); } /** @@ -68,21 +70,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 @@ -160,6 +154,11 @@ public String getContextParameter(ConversionContext conversionContext) { return null; } + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + /** * hashCode based on class * @@ -191,7 +190,7 @@ 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; @@ -254,11 +253,6 @@ public Type getDefiningType() { return null; } - @Override - public MapperConfiguration getMapperConfiguration() { - return null; - } - @Override public boolean isLifecycleCallbackMethod() { return false; @@ -270,16 +264,22 @@ public boolean isUpdateMethod() { } @Override - public MappingOptions getMappingOptions() { - return MappingOptions.empty(); + public MappingMethodOptions getOptions() { + return MappingMethodOptions.empty(); } - public BuiltInFieldReference getFieldReference() { + public FieldReference getFieldReference() { return null; } - public BuiltInConstructorFragment getConstructorFragment() { + 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 c8cd89169b..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 @@ -16,6 +16,8 @@ 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 AbstractToXmlGregorianCalendar { 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 036edd83a2..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 @@ -16,6 +16,8 @@ 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 AbstractToXmlGregorianCalendar { 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 1ae3efe3c9..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 @@ -6,26 +6,24 @@ 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 ); + JaxbElemToValue(Type type) { + this.parameter = new Parameter( "element", type ); + this.returnType = type.getTypeParameters().get( 0 ); this.importTypes = asSet( parameter.getType() ); } 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 index d0b06300c4..ef57e03175 100644 --- 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 @@ -13,6 +13,8 @@ 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 { 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 index fa87bff2db..285ebb29fe 100644 --- 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 @@ -6,16 +6,18 @@ package org.mapstruct.ap.internal.model.source.builtin; import java.util.Set; -import javax.xml.datatype.DatatypeConstants; 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 { @@ -28,7 +30,7 @@ public JodaLocalDateTimeToXmlGregorianCalendar(TypeFactory typeFactory) { this.parameter = new Parameter( "dt", typeFactory.getType( JodaTimeConstants.LOCAL_DATE_TIME_FQN ) ); this.importTypes = asSet( parameter.getType(), - typeFactory.getType( DatatypeConstants.class ) + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ) ); } 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 index e958ca952e..8163aa2fb2 100644 --- 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 @@ -6,16 +6,18 @@ package org.mapstruct.ap.internal.model.source.builtin; import java.util.Set; -import javax.xml.datatype.DatatypeConstants; 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 { @@ -28,7 +30,7 @@ public JodaLocalDateToXmlGregorianCalendar(TypeFactory typeFactory) { this.parameter = new Parameter( "dt", typeFactory.getType( JodaTimeConstants.LOCAL_DATE_FQN ) ); this.importTypes = asSet( parameter.getType(), - typeFactory.getType( DatatypeConstants.class ) + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ) ); } 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 index e9daac39c8..4fca4e0ed3 100644 --- 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 @@ -6,16 +6,18 @@ package org.mapstruct.ap.internal.model.source.builtin; import java.util.Set; -import javax.xml.datatype.DatatypeConstants; 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 { @@ -28,7 +30,7 @@ public JodaLocalTimeToXmlGregorianCalendar(TypeFactory typeFactory) { this.parameter = new Parameter( "dt", typeFactory.getType( JodaTimeConstants.LOCAL_TIME_FQN ) ); this.importTypes = asSet( parameter.getType(), - typeFactory.getType( DatatypeConstants.class ) + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ) ); } 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 index 96c8a8a31b..e2ec7d7696 100644 --- 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 @@ -7,15 +7,17 @@ import java.time.LocalDate; import java.util.Set; -import javax.xml.datatype.DatatypeConstants; 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 { @@ -28,7 +30,7 @@ public LocalDateToXmlGregorianCalendar(TypeFactory typeFactory) { this.parameter = new Parameter( "localDate", typeFactory.getType( LocalDate.class ) ); this.importTypes = asSet( parameter.getType(), - typeFactory.getType( DatatypeConstants.class ) + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ) ); } 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 index a302b59375..e4f00f7d57 100644 --- 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 @@ -5,7 +5,9 @@ */ package org.mapstruct.ap.internal.model.source.builtin; -public class NewDatatypeFactoryConstructorFragment implements BuiltInConstructorFragment { +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 9f0d2ad207..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 @@ -19,6 +19,8 @@ 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 AbstractToXmlGregorianCalendar { 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 f4d8ceee9b..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 @@ -7,15 +7,17 @@ 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 { @@ -25,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 7f270be5f6..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 @@ -7,15 +7,17 @@ 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 { @@ -25,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 index ab2db49e8a..93ec3e8f96 100644 --- 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 @@ -5,18 +5,20 @@ */ package org.mapstruct.ap.internal.model.source.builtin; +import java.util.Calendar; import java.util.Set; -import javax.xml.datatype.DatatypeConstants; -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.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 { @@ -26,10 +28,10 @@ public class XmlGregorianCalendarToJodaDateTime extends BuiltInMethod { private final Set importTypes; public XmlGregorianCalendarToJodaDateTime(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( JodaTimeConstants.DATE_TIME_FQN ); this.importTypes = asSet( - typeFactory.getType( DatatypeConstants.class ), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), typeFactory.getType( JodaTimeConstants.DATE_TIME_ZONE_FQN ), returnType, parameter.getType() ); 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 index d26cde0215..9a9c1561fc 100644 --- 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 @@ -6,17 +6,18 @@ package org.mapstruct.ap.internal.model.source.builtin; import java.util.Set; -import javax.xml.datatype.DatatypeConstants; -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.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 { @@ -26,10 +27,10 @@ public class XmlGregorianCalendarToJodaLocalDate extends BuiltInMethod { private final Set importTypes; public XmlGregorianCalendarToJodaLocalDate(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( JodaTimeConstants.LOCAL_DATE_FQN ); this.importTypes = asSet( - typeFactory.getType( DatatypeConstants.class ), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), returnType, parameter.getType() ); } 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 index 700c19c211..a8d36a13cf 100644 --- 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 @@ -6,17 +6,18 @@ package org.mapstruct.ap.internal.model.source.builtin; import java.util.Set; -import javax.xml.datatype.DatatypeConstants; -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.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 { @@ -26,10 +27,10 @@ public class XmlGregorianCalendarToJodaLocalDateTime extends BuiltInMethod { private final Set importTypes; public XmlGregorianCalendarToJodaLocalDateTime(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( JodaTimeConstants.LOCAL_DATE_TIME_FQN ); this.importTypes = asSet( - typeFactory.getType( DatatypeConstants.class ), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), returnType, parameter.getType() ); } 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 index b3eae20314..b26450d560 100644 --- 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 @@ -6,17 +6,18 @@ package org.mapstruct.ap.internal.model.source.builtin; import java.util.Set; -import javax.xml.datatype.DatatypeConstants; -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.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 { @@ -26,10 +27,10 @@ public class XmlGregorianCalendarToJodaLocalTime extends BuiltInMethod { private final Set importTypes; public XmlGregorianCalendarToJodaLocalTime(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( JodaTimeConstants.LOCAL_TIME_FQN ); this.importTypes = asSet( - typeFactory.getType( DatatypeConstants.class ), + typeFactory.getType( XmlConstants.JAVAX_XML_DATATYPE_CONSTANTS ), returnType, parameter.getType() ); } 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 index 1da6951b9a..7bcd5902a5 100644 --- 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 @@ -7,15 +7,17 @@ import java.time.LocalDate; 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 LocalDate}. + * * @author Gunnar Morling */ public class XmlGregorianCalendarToLocalDate extends BuiltInMethod { @@ -25,7 +27,7 @@ public class XmlGregorianCalendarToLocalDate extends BuiltInMethod { private final Set importTypes; public XmlGregorianCalendarToLocalDate(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( LocalDate.class ); this.importTypes = asSet( returnType, parameter.getType() ); } 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 73ac51fa2a..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 @@ -8,16 +8,18 @@ import java.text.SimpleDateFormat; 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 { @@ -27,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/ZonedDateTimeToXmlGregorianCalendar.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/ZonedDateTimeToXmlGregorianCalendar.java index 27a39cd2b7..d9e39b388e 100644 --- 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 @@ -16,6 +16,8 @@ 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 { 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 ab27029099..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 @@ -7,6 +7,10 @@ *

      * 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 166b7396ee..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 @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -29,12 +28,12 @@ public class CreateOrUpdateSelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { - - if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() ) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); + if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() + || criteria.isSourceParameterCheckRequired() + || criteria.isPresenceCheckRequired() ) { return methods; } @@ -49,10 +48,8 @@ public List> getMatchingMethods(Method mapp 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 index 4e52731225..50896195f1 100644 --- 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 @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -21,11 +20,9 @@ public class FactoryParameterSelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - ListsourceTypes, - Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); if ( !criteria.isObjectFactoryRequired() || methods.size() <= 1 ) { 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 3866857bbe..9a4fba6e30 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 @@ -22,26 +22,22 @@ public class InheritanceSelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { - if ( sourceTypes.size() != 1 ) { + Type sourceType = context.getSourceType(); + if ( sourceType == null ) { return methods; } - Type singleSourceType = first( sourceTypes ); - List> candidatesWithBestMatchingSourceType = new ArrayList<>(); int bestMatchingSourceTypeDistance = Integer.MAX_VALUE; - // find the methods with the minimum distance regarding getParameter getParameter type + // Find methods with the minimum inheritance distance from the source parameter type for ( SelectedMethod method : methods ) { Parameter singleSourceParam = first( method.getMethod().getSourceParameters() ); - int sourceTypeDistance = singleSourceType.distanceTo( singleSourceParam.getType() ); + int sourceTypeDistance = sourceType.distanceTo( singleSourceParam.getType() ); bestMatchingSourceTypeDistance = addToCandidateListIfMinimal( candidatesWithBestMatchingSourceType, @@ -53,17 +49,17 @@ public List> getMatchingMethods(Method mapp return candidatesWithBestMatchingSourceType; } - private int addToCandidateListIfMinimal(List> candidatesWithBestMathingType, + 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..df5cd848a5 --- /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..1d02e97e90 --- /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 index c4a029da90..691199b49d 100644 --- 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 @@ -8,7 +8,7 @@ import java.util.ArrayList; import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.model.source.Method; /** @@ -20,15 +20,29 @@ public class MethodFamilySelector implements MethodSelector { @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type targetType, SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); List> result = new ArrayList<>( methods.size() ); for ( SelectedMethod method : methods ) { - if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired() - && method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired() ) { + 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 ); } 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 587a37a7fe..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 @@ -7,7 +7,6 @@ import java.util.List; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -23,15 +22,10 @@ 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 candidates list of available methods - * @param sourceTypes parameter type(s) that should be matched - * @param targetType result type that should be matched - * @param criteria criteria used in the selection process + * @param context the context for the matching * @return list of methods that passes the matching process */ - List> getMatchingMethods(Method mappingMethod, - List> candidates, - List sourceTypes, - 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 e0f7d0328c..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 @@ -8,13 +8,12 @@ import java.util.ArrayList; 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. @@ -25,33 +24,40 @@ public class MethodSelectors { private final List selectors; - public MethodSelectors(Types typeUtils, Elements elementUtils, TypeFactory typeFactory, - FormattingMessager messager) { - selectors = Arrays.asList( + public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils, + FormattingMessager messager, Options options) { + List selectorList = new ArrayList<>( Arrays.asList( new MethodFamilySelector(), - new TypeSelector( typeFactory, messager ), + new TypeSelector( messager ), new QualifierSelector( typeUtils, elementUtils ), - new TargetTypeSelector( typeUtils, elementUtils ), - new XmlElementDeclSelector( typeUtils ), - new InheritanceSelector(), + 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 FactoryParameterSelector() ); + new SourceRhsSelector(), + new FactoryParameterSelector(), + new MostSpecificResultTypeSelector() + ) ); + this.selectors = selectorList; } /** * 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 sourceTypes parameter type(s) that should be matched - * @param targetType return type that should be matched - * @param criteria criteria used in the selection process + * @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(Method mappingMethod, List methods, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List methods, + SelectionContext context) { List> candidates = new ArrayList<>( methods.size() ); for ( T method : methods ) { @@ -59,12 +65,7 @@ public List> getMatchingMethods(Method mapp } for ( MethodSelector selector : selectors ) { - candidates = selector.getMatchingMethods( - mappingMethod, - candidates, - sourceTypes, - 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..87ec3b4dc1 --- /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 5c24e423ed..f2f5b591d6 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 @@ -9,18 +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.util.ElementUtils; +import org.mapstruct.ap.internal.util.TypeUtils; 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.NamedPrism; -import org.mapstruct.ap.internal.prism.QualifierPrism; +import org.mapstruct.ap.internal.gem.NamedGem; +import org.mapstruct.ap.internal.gem.QualifierGem; /** * This selector selects a best match based on qualifier annotations. @@ -41,19 +40,18 @@ */ 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, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); int numberOfQualifiersToMatch = 0; @@ -117,8 +115,8 @@ public List> getMatchingMethods(Method mapp // 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. - NamedPrism namedPrism = NamedPrism.getInstance( qualifierAnnotationMirror ); - if ( namedPrism.value() != null && qualfiedByNames.contains( namedPrism.value() ) ) { + 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++; } @@ -168,7 +166,7 @@ private Set getQualifierAnnotationMirrors( Method 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 ) { + 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 index bc0696d2b2..1702a8d4d6 100644 --- 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 @@ -6,6 +6,7 @@ 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; @@ -13,6 +14,8 @@ /** * 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 { @@ -39,4 +42,21 @@ public void setParameterBindings(List parameterBindings) { 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 c632fec6c4..ecc9ac9e01 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 @@ -5,12 +5,13 @@ */ package org.mapstruct.ap.internal.model.source.selector; -import java.util.ArrayList; +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; /** @@ -20,54 +21,79 @@ */ public class SelectionCriteria { - private final List qualifiers = new ArrayList<>(); - private final List qualifiedByNames = new ArrayList<>(); + private final QualifyingInfo qualifyingInfo; private final String targetPropertyName; - private final TypeMirror qualifyingResultType; private final SourceRHS sourceRHS; - private boolean preferUpdateMapping; - private final boolean objectFactoryRequired; - private final boolean lifecycleCallbackRequired; - - public SelectionCriteria(SelectionParameters selectionParameters, String targetPropertyName, - boolean preferUpdateMapping, boolean objectFactoryRequired, - boolean lifecycleCallbackRequired) { - if ( selectionParameters != null ) { - qualifiers.addAll( selectionParameters.getQualifiers() ); - qualifiedByNames.addAll( selectionParameters.getQualifyingNames() ); - qualifyingResultType = selectionParameters.getResultType(); - sourceRHS = selectionParameters.getSourceRHS(); - } - else { - this.qualifyingResultType = null; - sourceRHS = null; - } + private boolean ignoreQualifiers = false; + private Type type; + private final MappingControl mappingControl; + + 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.preferUpdateMapping = preferUpdateMapping; - this.objectFactoryRequired = objectFactoryRequired; - this.lifecycleCallbackRequired = lifecycleCallbackRequired; + 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 objectFactoryRequired; + return type == Type.OBJECT_FACTORY; } /** * @return true if lifecycle callback methods should be selected, false otherwise. */ public boolean isLifecycleCallbackRequired() { - return lifecycleCallbackRequired; + 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 qualifiedByNames; + return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiedByNames(); } public String getTargetPropertyName() { @@ -75,11 +101,11 @@ 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() { @@ -87,24 +113,131 @@ public SourceRHS getSourceRHS() { } public void setPreferUpdateMapping(boolean preferUpdateMapping) { - this.preferUpdateMapping = preferUpdateMapping; + this.type = preferUpdateMapping ? Type.PREFER_UPDATE_MAPPING : null; } public boolean hasQualfiers() { - return !qualifiedByNames.isEmpty() || !qualifiers.isEmpty(); + 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, targetPropertyName, preferUpdateMapping, false, false ); + return new SelectionCriteria( + selectionParameters, + mappingControl, + targetPropertyName, + preferUpdateMapping ? Type.PREFER_UPDATE_MAPPING : null + ); } public static SelectionCriteria forFactoryMethods(SelectionParameters selectionParameters) { - return new SelectionCriteria( selectionParameters, null, false, true, false ); + return new SelectionCriteria( selectionParameters, null, null, Type.OBJECT_FACTORY ); } public static SelectionCriteria forLifecycleMethods(SelectionParameters selectionParameters) { - return new SelectionCriteria( selectionParameters, null, false, false, true ); + 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 3b97b70323..0afae641db 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 @@ -9,10 +9,8 @@ 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.util.TypeUtils; -import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; /** @@ -25,17 +23,16 @@ */ 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, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(List> methods, + SelectionContext context) { + SelectionCriteria criteria = context.getSelectionCriteria(); TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType(); if ( qualifyingTypeMirror != null && !criteria.isLifecycleCallbackRequired() ) { 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 c00a90760d..50f834d43e 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 @@ -11,9 +11,7 @@ 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.MethodMatcher; import org.mapstruct.ap.internal.util.FormattingMessager; @@ -29,38 +27,24 @@ */ public class TypeSelector implements MethodSelector { - private TypeFactory typeFactory; private FormattingMessager messager; - public TypeSelector(TypeFactory typeFactory, FormattingMessager messager) { - this.typeFactory = typeFactory; + public TypeSelector(FormattingMessager messager) { this.messager = messager; } @Override - public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { - + public List> getMatchingMethods(List> methods, + SelectionContext context) { if ( methods.isEmpty() ) { return methods; } + Type returnType = context.getReturnType(); + List> result = new ArrayList<>(); - List availableBindings; - if ( sourceTypes.isEmpty() ) { - // if no source types are given, we have a factory or lifecycle method - availableBindings = getAvailableParameterBindingsFromMethod( - mappingMethod, - targetType, - criteria.getSourceRHS() - ); - } - else { - availableBindings = getAvailableParameterBindingsFromSourceTypes( sourceTypes, targetType, mappingMethod ); - } + List availableBindings = context.getAvailableParameterBindings(); for ( SelectedMethod method : methods ) { List> parameterBindingPermutations = @@ -68,7 +52,7 @@ public List> getMatchingMethods(Method mapp if ( parameterBindingPermutations != null ) { SelectedMethod matchingMethod = - getMatchingParameterBinding( targetType, mappingMethod, method, parameterBindingPermutations ); + getMatchingParameterBinding( returnType, context, method, parameterBindingPermutations ); if ( matchingMethod != null ) { result.add( matchingMethod ); @@ -78,73 +62,8 @@ public List> getMatchingMethods(Method mapp return result; } - private List getAvailableParameterBindingsFromMethod(Method method, Type targetType, - SourceRHS sourceRHS) { - List availableParams = new ArrayList<>( method.getParameters().size() + 3 ); - - if ( sourceRHS != null ) { - availableParams.addAll( ParameterBinding.fromParameters( method.getContextParameters() ) ); - availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) ); - } - else { - availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); - } - - addMappingTargetAndTargetTypeBindings( availableParams, targetType ); - - return availableParams; - } - - private List getAvailableParameterBindingsFromSourceTypes(List sourceTypes, - Type targetType, Method mappingMethod) { - - List availableParams = new ArrayList<>( sourceTypes.size() + 2 ); - - for ( Type sourceType : sourceTypes ) { - availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); - } - - for ( Parameter param : mappingMethod.getParameters() ) { - if ( param.isMappingContext() ) { - availableParams.add( ParameterBinding.fromParameter( param ) ); - } - } - - addMappingTargetAndTargetTypeBindings( availableParams, targetType ); - - return availableParams; - } - - /** - * 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 void addMappingTargetAndTargetTypeBindings(List availableParams, Type targetType) { - boolean mappingTargetAvailable = false; - boolean targetTypeAvailable = 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; - } - } - - if ( !mappingTargetAvailable ) { - availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) ); - } - if ( !targetTypeAvailable ) { - availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); - } - } - - private SelectedMethod getMatchingParameterBinding(Type targetType, - Method mappingMethod, SelectedMethod selectedMethodInfo, + private SelectedMethod getMatchingParameterBinding(Type returnType, + SelectionContext context, SelectedMethod selectedMethodInfo, List> parameterAssignmentVariants) { List> matchingParameterAssignmentVariants = new ArrayList<>( @@ -155,7 +74,7 @@ private SelectedMethod getMatchingParameterBinding(Type ta // remove all assignment variants that doesn't match the types from the method matchingParameterAssignmentVariants.removeIf( parameterAssignments -> - !selectedMethod.matches( extractTypes( parameterAssignments ), targetType ) + !selectedMethod.matches( extractTypes( parameterAssignments ), returnType ) ); if ( matchingParameterAssignmentVariants.isEmpty() ) { @@ -187,7 +106,7 @@ else if ( matchingParameterAssignmentVariants.size() == 1 ) { messager.printMessage( selectedMethod.getExecutable(), Message.LIFECYCLEMETHOD_AMBIGUOUS_PARAMETERS, - mappingMethod + context.getMappingMethod() ); return null; @@ -295,7 +214,9 @@ private static List findCandidateBindingsForParameter(List *
    13. Name and Scope matches
    14. @@ -35,26 +32,29 @@ * 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, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + 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( mappingMethod.getResultType(), criteria.getTargetPropertyName() ); + findXmlElementRef( resultType, targetPropertyName ); for ( SelectedMethod candidate : methods ) { if ( !( candidate.getMethod() instanceof SourceMethod ) ) { @@ -62,14 +62,14 @@ public List> getMatchingMethods(Method mapp } SourceMethod candidateMethod = (SourceMethod) candidate.getMethod(); - XmlElementDeclPrism xmlElememtDecl = XmlElementDeclPrism.getInstanceOn( candidateMethod.getExecutable() ); + XmlElementDeclInfo xmlElementDeclInfo = getXmlElementDeclInfo( candidateMethod.getExecutable() ); - if ( xmlElememtDecl == null ) { + if ( xmlElementDeclInfo == null ) { continue; } - String name = xmlElememtDecl.name(); - TypeMirror scope = xmlElememtDecl.scope(); + String name = xmlElementDeclInfo.nameValue(); + TypeMirror scope = xmlElementDeclInfo.scopeType(); boolean nameIsSetAndMatches = name != null && name.equals( xmlElementRefInfo.nameValue() ); boolean scopeIsSetAndMatches = @@ -88,13 +88,13 @@ 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 { @@ -140,9 +140,9 @@ private XmlElementRefInfo findXmlElementRef(Type resultType, String targetProper for ( Element enclosed : currentElement.getEnclosedElements() ) { if ( enclosed.getKind().equals( ElementKind.FIELD ) && enclosed.getSimpleName().contentEquals( targetPropertyName ) ) { - XmlElementRefPrism xmlElementRef = XmlElementRefPrism.getInstanceOn( enclosed ); - if ( xmlElementRef != null ) { - return new XmlElementRefInfo( xmlElementRef.name(), currentMirror ); + XmlElementRefInfo xmlElementRefInfo = getXmlElementRefInfo( enclosed ); + if ( xmlElementRefInfo != null ) { + return new XmlElementRefInfo( xmlElementRefInfo.nameValue(), currentMirror ); } } } @@ -152,7 +152,11 @@ private XmlElementRefInfo findXmlElementRef(Type resultType, String targetProper return defaultInfo; } - private static class XmlElementRefInfo { + abstract XmlElementDeclInfo getXmlElementDeclInfo(Element element); + + abstract XmlElementRefInfo getXmlElementRefInfo(Element element); + + static class XmlElementRefInfo { private final String nameValue; private final TypeMirror sourceType; @@ -161,12 +165,37 @@ private static class XmlElementRefInfo { this.sourceType = sourceType; } - public String nameValue() { + String nameValue() { return nameValue; } - public TypeMirror sourceType() { + 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/option/MappingOption.java b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.java new file mode 100644 index 0000000000..fedbe997a6 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/option/MappingOption.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.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"), + ; + // 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 1e555e8d37..38474baf9c 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 @@ -5,54 +5,90 @@ */ package org.mapstruct.ap.internal.option; -import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; +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 ReportingPolicyPrism unmappedTargetPolicy; - private final boolean alwaysGenerateSpi; - private final String defaultComponentModel; - private final boolean verbose; - - public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment, - ReportingPolicyPrism unmappedTargetPolicy, - String defaultComponentModel, boolean alwaysGenerateSpi, boolean verbose) { - this.suppressGeneratorTimestamp = suppressGeneratorTimestamp; - this.suppressGeneratorVersionComment = suppressGeneratorVersionComment; - this.unmappedTargetPolicy = unmappedTargetPolicy; - this.defaultComponentModel = defaultComponentModel; - this.alwaysGenerateSpi = alwaysGenerateSpi; - this.verbose = verbose; + + 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 ReportingPolicyGem getUnmappedTargetPolicy() { + return parseEnum( MappingOption.UNMAPPED_TARGET_POLICY, ReportingPolicyGem.class ); } - public ReportingPolicyPrism getUnmappedTargetPolicy() { - return unmappedTargetPolicy; + 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 verbose; + 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 ); + } + + 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/prism/CollectionMappingStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/CollectionMappingStrategyPrism.java deleted file mode 100644 index 457607d7c7..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/CollectionMappingStrategyPrism.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 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.prism; - -/** - * Prism for the enum {@link org.mapstruct.CollectionMappingStrategy} - * - * @author Andreas Gudian - */ -public enum CollectionMappingStrategyPrism { - - ACCESSOR_ONLY, - SETTER_PREFERRED, - ADDER_PREFERRED, - TARGET_IMMUTABLE; -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/InjectionStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/InjectionStrategyPrism.java deleted file mode 100644 index 839bc26f9c..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/InjectionStrategyPrism.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 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.prism; - -/** - * Prism for the enum {@link org.mapstruct.InjectionStrategy}. - * - * @author Kevin Grüneberg - */ -public enum InjectionStrategyPrism { - - FIELD, - CONSTRUCTOR; -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingConstantsPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingConstantsPrism.java deleted file mode 100644 index c03bac6a7e..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingConstantsPrism.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 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.prism; - -/** - * Prism for the enum {@link org.mapstruct.MappingConstants} - * - * @author Sjaak Derksen - */ -public final class MappingConstantsPrism { - - private MappingConstantsPrism() { - } - - public static final String NULL = ""; - - public static final String ANY_REMAINING = ""; - - public static final String ANY_UNMAPPED = ""; -} 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 7ea4ce8bbc..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/MappingInheritanceStrategyPrism.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 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.prism; - - -/** - * Prism for the enum {@link org.mapstruct.MappingInheritanceStrategy} - * - * @author Andreas Gudian - */ -public enum MappingInheritanceStrategyPrism { - - 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; - - MappingInheritanceStrategyPrism(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/prism/NullValueCheckStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValueCheckStrategyPrism.java deleted file mode 100644 index 555231f987..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValueCheckStrategyPrism.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 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.prism; - - -/** - * Prism for the enum {@link org.mapstruct.NullValueCheckStrategy} - * - * @author Sean Huang - */ -public enum NullValueCheckStrategyPrism { - - ON_IMPLICIT_CONVERSION, - ALWAYS; -} 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 b0f11bcc7c..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValueMappingStrategyPrism.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 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.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/NullValuePropertyMappingStrategyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValuePropertyMappingStrategyPrism.java deleted file mode 100644 index bc0cdae9e7..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/NullValuePropertyMappingStrategyPrism.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 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.prism; - - -/** - * Prism for the enum {@link org.mapstruct.NullValuePropertyMappingStrategy} - * - * @author Sjaak Derksen - */ -public enum NullValuePropertyMappingStrategyPrism { - - SET_TO_NULL, - SET_TO_DEFAULT, - IGNORE; -} 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 f40cedd20a..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 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.prism; - -import javax.xml.bind.annotation.XmlElementDecl; -import javax.xml.bind.annotation.XmlElementRef; - -import org.mapstruct.AfterMapping; -import org.mapstruct.BeanMapping; -import org.mapstruct.BeforeMapping; -import org.mapstruct.Builder; -import org.mapstruct.Context; -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.Named; -import org.mapstruct.ObjectFactory; -import org.mapstruct.Qualifier; -import org.mapstruct.TargetType; -import org.mapstruct.ValueMapping; -import org.mapstruct.ValueMappings; - -import net.java.dev.hickory.prism.GeneratePrism; -import net.java.dev.hickory.prism.GeneratePrisms; - -/** - * 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 = Named.class, publicAccess = true), - @GeneratePrism(value = ObjectFactory.class, publicAccess = true), - @GeneratePrism(value = AfterMapping.class, publicAccess = true), - @GeneratePrism(value = BeforeMapping.class, publicAccess = true), - @GeneratePrism(value = ValueMapping.class, publicAccess = true), - @GeneratePrism(value = ValueMappings.class, publicAccess = true), - @GeneratePrism(value = Context.class, publicAccess = true), - @GeneratePrism(value = Builder.class, publicAccess = true), - - // external types - @GeneratePrism(value = XmlElementDecl.class, publicAccess = true), - @GeneratePrism(value = XmlElementRef.class, publicAccess = true) -}) -public class PrismGenerator { - -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/ReportingPolicyPrism.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/ReportingPolicyPrism.java deleted file mode 100644 index 8c06231447..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/ReportingPolicyPrism.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 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.prism; - -import javax.tools.Diagnostic; -import javax.tools.Diagnostic.Kind; - -/** - * Prism for the enum {@link org.mapstruct.ReportingPolicy}. - * - * @author Gunnar Morling - */ -public enum ReportingPolicyPrism { - - 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; - - ReportingPolicyPrism(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/prism/package-info.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/package-info.java deleted file mode 100644 index 4c5a19a855..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/* - * 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 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 a4d9704369..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 @@ -11,10 +11,12 @@ 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; @@ -23,8 +25,7 @@ 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.prism.InjectionStrategyPrism; -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 @@ -42,10 +43,10 @@ public abstract class AnnotationBasedComponentModelProcessor implements ModelEle public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, Mapper mapper) { this.typeFactory = context.getTypeFactory(); - MapperConfiguration mapperConfiguration = MapperConfiguration.getInstanceOn( mapperTypeElement ); + MapperOptions mapperAnnotation = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() ); - String componentModel = mapperConfiguration.componentModel( context.getOptions() ); - InjectionStrategyPrism injectionStrategy = mapperConfiguration.getInjectionStrategy(); + String componentModel = mapperAnnotation.componentModel(); + InjectionStrategyGem injectionStrategy = mapperAnnotation.getInjectionStrategy(); if ( !getComponentModelIdentifier().equalsIgnoreCase( componentModel ) ) { return mapper; @@ -74,17 +75,20 @@ else if ( mapper.getDecorator() != null ) { } } - if ( injectionStrategy == InjectionStrategyPrism.CONSTRUCTOR ) { + if ( injectionStrategy == InjectionStrategyGem.CONSTRUCTOR ) { buildConstructors( mapper ); } + else if ( injectionStrategy == InjectionStrategyGem.SETTER ) { + buildSetters( mapper ); + } return mapper; } - protected void adjustDecorator(Mapper mapper, InjectionStrategyPrism injectionStrategy) { + protected void adjustDecorator(Mapper mapper, InjectionStrategyGem injectionStrategy) { Decorator decorator = mapper.getDecorator(); - for ( Annotation typeAnnotation : getDecoratorAnnotations() ) { + for ( Annotation typeAnnotation : getDecoratorAnnotations( decorator ) ) { decorator.addAnnotation( typeAnnotation ); } @@ -110,6 +114,42 @@ private List toMapperReferences(List fields) { 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 ); @@ -176,7 +216,6 @@ private AnnotatedConstructor buildAnnotatedConstructorForDecorator(Decorator dec ); } - /** * 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 @@ -198,17 +237,33 @@ private void removeDuplicateAnnotations(List annotati AnnotationMapperReference annotationMapperReference = mapperReferenceIterator.next(); mapperReferenceIterator.remove(); - List qualifiers = new ArrayList<>(); - for ( Annotation annotation : annotationMapperReference.getAnnotations() ) { - if ( !mapperReferenceAnnotationsTypes.contains( annotation.getType() ) ) { - qualifiers.add( annotation ); - } - } + 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; } @@ -220,15 +275,15 @@ protected List getDelegatorReferenceAnnotations(Mapper mapper) { /** * @param originalReference the reference to be replaced * @param annotations the list of annotations - * @param injectionStrategyPrism strategy for injection + * @param injectionStrategy strategy for injection * @return the mapper reference replacing the original one */ protected Field replacementMapperReference(Field originalReference, List annotations, - InjectionStrategyPrism injectionStrategyPrism) { + InjectionStrategyGem injectionStrategy) { boolean finalField = - injectionStrategyPrism == InjectionStrategyPrism.CONSTRUCTOR && !additionalPublicEmptyConstructor(); + injectionStrategy == InjectionStrategyGem.CONSTRUCTOR && !additionalPublicEmptyConstructor(); - boolean includeAnnotationsOnField = injectionStrategyPrism == InjectionStrategyPrism.FIELD; + boolean includeAnnotationsOnField = injectionStrategy == InjectionStrategyGem.FIELD; return new AnnotationMapperReference( originalReference.getType(), @@ -253,7 +308,7 @@ protected Field replacementMapperReference(Field originalReference, 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 a5e3458681..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 @@ -9,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} @@ -23,19 +26,19 @@ 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 @@ -47,4 +50,24 @@ protected boolean requiresGenerationOfDecoratorClass() { 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 bc7374d391..8ac1cc067e 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 @@ -13,19 +13,21 @@ 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.RoundContext; -import org.mapstruct.ap.internal.util.workarounds.TypesDecorator; +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. @@ -39,23 +41,29 @@ public class DefaultModelElementProcessorContext implements ProcessorContext { private final Options options; private final TypeFactory typeFactory; private final VersionInformation versionInformation; - private final Types delegatingTypes; + private final TypeUtils delegatingTypes; + private final ElementUtils delegatingElements; private final AccessorNamingUtils accessorNaming; + private final RoundContext roundContext; public DefaultModelElementProcessorContext(ProcessingEnvironment processingEnvironment, Options options, - RoundContext roundContext, Map notToBeImported) { + RoundContext roundContext, Map notToBeImported, TypeElement mapperElement) { this.processingEnvironment = processingEnvironment; this.messager = new DelegatingMessager( processingEnvironment.getMessager(), options.isVerbose() ); this.accessorNaming = roundContext.getAnnotationProcessorContext().getAccessorNaming(); this.versionInformation = DefaultVersionInformation.fromProcessingEnvironment( processingEnvironment ); - this.delegatingTypes = new TypesDecorator( processingEnvironment, versionInformation ); + this.delegatingTypes = TypeUtils.create( processingEnvironment, versionInformation ); + this.delegatingElements = ElementUtils.create( processingEnvironment, versionInformation, mapperElement ); + this.roundContext = roundContext; this.typeFactory = new TypeFactory( - processingEnvironment.getElementUtils(), + delegatingElements, delegatingTypes, messager, roundContext, - notToBeImported + notToBeImported, + options.isVerbose(), + versionInformation ); this.options = options; } @@ -66,13 +74,13 @@ public Filer getFiler() { } @Override - public Types getTypeUtils() { + public TypeUtils getTypeUtils() { return delegatingTypes; } @Override - public Elements getElementUtils() { - return processingEnvironment.getElementUtils(); + public ElementUtils getElementUtils() { + return delegatingElements; } @Override @@ -90,6 +98,16 @@ public AccessorNamingUtils getAccessorNaming() { return accessorNaming; } + @Override + public Map getEnumTransformationStrategies() { + return roundContext.getAnnotationProcessorContext().getEnumTransformationStrategies(); + } + + @Override + public EnumMappingStrategy getEnumMappingStrategy() { + return roundContext.getAnnotationProcessorContext().getEnumMappingStrategy(); + } + @Override public Options getOptions() { return options; @@ -167,6 +185,7 @@ public void note( int level, Message msg, Object... args ) { } } + @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 60e5f112d2..0457a66da4 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 @@ -6,6 +6,8 @@ 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; @@ -39,6 +41,8 @@ public class DefaultVersionInformation implements VersionInformation { private final String runtimeVendor; private final String compiler; private final boolean sourceVersionAtLeast9; + private final boolean sourceVersionAtLeast11; + private final boolean sourceVersionAtLeast19; private final boolean eclipseJDT; private final boolean javac; @@ -51,6 +55,8 @@ public class DefaultVersionInformation implements VersionInformation { 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.sourceVersionAtLeast19 = sourceVersion.compareTo( SourceVersion.RELEASE_6 ) > 12; } @Override @@ -78,6 +84,16 @@ public boolean isSourceVersionAtLeast9() { return sourceVersionAtLeast9; } + @Override + public boolean isSourceVersionAtLeast11() { + return sourceVersionAtLeast11; + } + + @Override + public boolean isSourceVersionAtLeast19() { + return sourceVersionAtLeast19; + } + @Override public boolean isEclipseJDTCompiler() { return eclipseJDT; @@ -103,7 +119,28 @@ static DefaultVersionInformation fromProcessingEnvironment(ProcessingEnvironment } 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 COMPILER_NAME_JAVAC; @@ -135,7 +172,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() ) ) { @@ -149,6 +189,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 4f7b46d643..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 @@ -9,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} @@ -23,7 +29,7 @@ public class Jsr330ComponentProcessor extends AnnotationBasedComponentModelProcessor { @Override protected String getComponentModelIdentifier() { - return "jsr330"; + return MappingConstantsGem.ComponentModelGem.JSR330; } @Override @@ -37,7 +43,7 @@ protected List getTypeAnnotations(Mapper mapper) { } @Override - protected List getDecoratorAnnotations() { + protected List getDecoratorAnnotations(Decorator decorator) { return Arrays.asList( singleton(), named() ); } @@ -57,21 +63,39 @@ protected boolean requiresGenerationOfDecoratorClass() { } private Annotation singleton() { - return new Annotation( getTypeFactory().getType( "javax.inject.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 1b31bcf4cb..03e8bf977f 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 @@ -6,6 +6,7 @@ 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; @@ -13,23 +14,34 @@ 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; @@ -41,25 +53,24 @@ 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.MappingInheritanceStrategyPrism; -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 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; @@ -73,8 +84,11 @@ */ 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; @@ -82,25 +96,33 @@ public class MapperCreationProcessor implements ModelElementProcessor 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.getEnumMappingStrategy(), + context.getEnumTransformationStrategies(), options, new MappingResolverImpl( messager, @@ -108,7 +130,8 @@ public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, L typeUtils, typeFactory, new ArrayList<>( sourceModel ), - mapperReferences + mapperReferences, + options.isVerbose() ), mapperTypeElement, //sourceModel is passed only to fetch the after/before mapping methods in lifecycleCallbackFactory; @@ -117,7 +140,7 @@ public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, L mapperReferences ); this.mappingContext = ctx; - return getMapper( mapperTypeElement, mapperConfig, sourceModel ); + return getMapper( mapperTypeElement, mapperOptions, sourceModel ); } @Override @@ -125,14 +148,15 @@ public int getPriority() { return 1000; } - private List initReferencedMappers(TypeElement element, MapperConfiguration mapperConfig) { + 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 ); @@ -144,19 +168,35 @@ private List initReferencedMappers(TypeElement element, MapperC return result; } - private Mapper getMapper(TypeElement element, MapperConfiguration mapperConfig, List methods) { + 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( mapperConfig, 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<>(); + Set supportingFieldSet = new LinkedHashSet<>(mappingContext.getUsedSupportedFields()); addAllFieldsIn( mappingContext.getUsedSupportedMappings(), supportingFieldSet ); fields.addAll( supportingFieldSet ); - // handle constructorfragments + // handle constructor fragments Set constructorFragments = new LinkedHashSet<>(); addAllFragmentsIn( mappingContext.getUsedSupportedMappings(), constructorFragments ); @@ -167,13 +207,15 @@ private Mapper getMapper(TypeElement element, MapperConfiguration mapperConfig, .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 ) ) .build(); if ( !mappingContext.getForgedMethodsUnderCreation().isEmpty() ) { @@ -181,21 +223,31 @@ private Mapper getMapper(TypeElement element, MapperConfiguration mapperConfig, 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() ); @@ -233,45 +285,49 @@ 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 ) + .decoratedWith( decoratedWith ) .methods( mappingMethods ) .hasDelegateConstructor( hasDelegateConstructor ) .options( options ) .versionInformation( versionInformation ) - .implName( implName ) - .implPackage( implPackage ) - .extraImports( getExtraImports( element ) ) + .implName( mapperOptions.implementationName() ) + .implPackage( mapperOptions.implementationPackage() ) + .extraImports( getExtraImports( element, mapperOptions ) ) + .suppressGeneratorTimestamp( mapperOptions.suppressTimestampInGenerated() ) + .additionalAnnotations( decoratorAnnotations ) .build(); return decorator; } - private SortedSet getExtraImports(TypeElement element) { + 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) { + private List getMappingMethods(MapperOptions mapperAnnotation, List methods) { List mappingMethods = new ArrayList<>(); for ( SourceMethod method : methods ) { @@ -279,14 +335,16 @@ private List getMappingMethods(MapperConfiguration mapperConfig, 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 iterableMappingMethod = createWithElementMappingMethod( method, mappingOptions, @@ -304,7 +362,7 @@ else if ( method.isMapMapping() ) { FormattingParameters keyFormattingParameters = null; SelectionParameters valueSelectionParameters = null; FormattingParameters valueFormattingParameters = null; - NullValueMappingStrategyPrism nullValueMappingStrategy = null; + NullValueMappingStrategyGem nullValueMappingStrategy = null; if ( mappingOptions.getMapMapping() != null ) { keySelectionParameters = mappingOptions.getMapMapping().getKeySelectionParameters(); @@ -322,7 +380,6 @@ else if ( method.isMapMapping() ) { .keySelectionParameters( keySelectionParameters ) .valueFormattingParameters( valueFormattingParameters ) .valueSelectionParameters( valueSelectionParameters ) - .nullValueMappingStrategy( nullValueMappingStrategy ) .build(); hasFactoryMethod = mapMappingMethod.getFactoryMethod() != null; @@ -335,24 +392,18 @@ else if ( method.isValueMapping() ) { .mappingContext( mappingContext ) .method( method ) .valueMappings( mappingOptions.getValueMappings() ) + .enumMapping( mappingOptions.getEnumMappingOptions() ) .build(); - mappingMethods.add( valueMappingMethod ); - } - else if ( method.isEnumMapping() ) { + if ( valueMappingMethod != null ) { + mappingMethods.add( valueMappingMethod ); + } + } + else if ( method.isRemovedEnumMapping() ) { messager.printMessage( method.getExecutable(), - Message.ENUMMAPPING_DEPRECATED ); - - EnumMappingMethod.Builder builder = new EnumMappingMethod.Builder(); - MappingMethod enumMappingMethod = builder - .mappingContext( mappingContext ) - .sourceMethod( method ) - .build(); - - if ( enumMappingMethod != null ) { - mappingMethods.add( enumMappingMethod ); - } + Message.ENUMMAPPING_REMOVED + ); } else if ( method.isStreamMapping() ) { this.messager.note( 1, Message.STREAMMAPPING_CREATE_NOTE, method ); @@ -369,16 +420,16 @@ else if ( method.isStreamMapping() ) { } else { this.messager.note( 1, Message.BEANMAPPING_CREATE_NOTE, method ); - BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder(); - BeanMappingMethod beanMappingMethod = builder + BeanMappingMethod.Builder beanMappingBuilder = new BeanMappingMethod.Builder(); + BeanMappingMethod beanMappingMethod = beanMappingBuilder .mappingContext( mappingContext ) .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 ) { - // 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; mappingMethods.add( beanMappingMethod ); } } @@ -393,17 +444,32 @@ else if ( method.isStreamMapping() ) { return mappingMethods; } + 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, - MappingOptions mappingOptions, ContainerMappingMethodBuilder builder) { + MappingMethodOptions mappingMethodOptions, ContainerMappingMethodBuilder builder) { FormattingParameters formattingParameters = null; SelectionParameters selectionParameters = null; - NullValueMappingStrategyPrism nullValueMappingStrategy = null; - if ( mappingOptions.getIterableMapping() != null ) { - formattingParameters = mappingOptions.getIterableMapping().getFormattingParameters(); - selectionParameters = mappingOptions.getIterableMapping().getSelectionParameters(); - nullValueMappingStrategy = mappingOptions.getIterableMapping().getNullValueMappingStrategy(); + if ( mappingMethodOptions.getIterableMapping() != null ) { + formattingParameters = mappingMethodOptions.getIterableMapping().getFormattingParameters(); + selectionParameters = mappingMethodOptions.getIterableMapping().getSelectionParameters(); } return builder @@ -411,12 +477,12 @@ private M createWithElementMappingMethod(Sour .method( method ) .formattingParameters( formattingParameters ) .selectionParameters( selectionParameters ) - .nullValueMappingStrategy( nullValueMappingStrategy ) .build(); } - private void mergeInheritedOptions(SourceMethod method, MapperConfiguration mapperConfig, - List availableMethods, List initializingMethods) { + private void mergeInheritedOptions(SourceMethod method, MapperOptions mapperConfig, + List availableMethods, List initializingMethods, + AnnotationMirror annotationMirror) { if ( initializingMethods.contains( method ) ) { // cycle detected @@ -431,61 +497,41 @@ private void mergeInheritedOptions(SourceMethod method, MapperConfiguration mapp initializingMethods.add( method ); - MappingOptions mappingOptions = method.getMappingOptions(); + MappingMethodOptions mappingOptions = method.getOptions(); List applicableReversePrototypeMethods = method.getApplicableReversePrototypeMethods(); - MappingOptions inverseMappingOptions = - getInverseMappingOptions( join( availableMethods, applicableReversePrototypeMethods ), + SourceMethod inverseTemplateMethod = + getInverseTemplateMethod( join( availableMethods, applicableReversePrototypeMethods ), method, initializingMethods, mapperConfig ); List applicablePrototypeMethods = method.getApplicablePrototypeMethods(); - MappingOptions forwardMappingOptions = - getTemplateMappingOptions( + SourceMethod forwardTemplateMethod = + getForwardTemplateMethod( join( availableMethods, applicablePrototypeMethods ), method, initializingMethods, mapperConfig ); // apply defined (@InheritConfiguration, @InheritInverseConfiguration) mappings - if ( forwardMappingOptions != null ) { - mappingOptions.applyInheritedOptions( - forwardMappingOptions, - false, - method, - messager, - typeFactory, - accessorNaming - ); + if ( forwardTemplateMethod != null ) { + mappingOptions.applyInheritedOptions( method, forwardTemplateMethod, false, annotationMirror ); } - if ( inverseMappingOptions != null ) { - mappingOptions.applyInheritedOptions( - inverseMappingOptions, - true, - method, - messager, - typeFactory, - accessorNaming - ); + if ( inverseTemplateMethod != null ) { + mappingOptions.applyInheritedOptions( method, inverseTemplateMethod, true, annotationMirror ); } // apply auto inherited options - MappingInheritanceStrategyPrism inheritanceStrategy = mapperConfig.getMappingInheritanceStrategy(); + MappingInheritanceStrategyGem inheritanceStrategy = mapperConfig.getMappingInheritanceStrategy(); if ( inheritanceStrategy.isAutoInherit() ) { // but.. there should not be an @InheritedConfiguration - if ( forwardMappingOptions == null && inheritanceStrategy.isApplyForward() ) { + if ( forwardTemplateMethod == null && inheritanceStrategy.isApplyForward() ) { if ( applicablePrototypeMethods.size() == 1 ) { - mappingOptions.applyInheritedOptions( - first( applicablePrototypeMethods ).getMappingOptions(), - false, - method, - messager, - typeFactory, - accessorNaming - ); + mappingOptions.applyInheritedOptions( method, first( applicablePrototypeMethods ), false, + annotationMirror ); } else if ( applicablePrototypeMethods.size() > 1 ) { messager.printMessage( @@ -496,16 +542,10 @@ else if ( applicablePrototypeMethods.size() > 1 ) { } // or no @InheritInverseConfiguration - if ( inverseMappingOptions == null && inheritanceStrategy.isApplyReverse() ) { + if ( inverseTemplateMethod == null && inheritanceStrategy.isApplyReverse() ) { if ( applicableReversePrototypeMethods.size() == 1 ) { - mappingOptions.applyInheritedOptions( - first( applicableReversePrototypeMethods ).getMappingOptions(), - true, - method, - messager, - typeFactory, - accessorNaming - ); + mappingOptions.applyInheritedOptions( method, first( applicableReversePrototypeMethods ), true, + annotationMirror ); } else if ( applicableReversePrototypeMethods.size() > 1 ) { messager.printMessage( @@ -517,14 +557,8 @@ else if ( applicableReversePrototypeMethods.size() > 1 ) { } // @BeanMapping( ignoreByDefault = true ) - if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().isignoreByDefault() ) { - mappingOptions.applyIgnoreAll( - mappingOptions, - method, - messager, - typeFactory, - accessorNaming - ); + if ( mappingOptions.getBeanMapping() != null && mappingOptions.getBeanMapping().isIgnoredByDefault() ) { + mappingOptions.applyIgnoreAll( method, typeFactory, mappingContext.getMessager() ); } mappingOptions.markAsFullyInitialized(); @@ -543,25 +577,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 + // method is configured as being inverse method, collect candidates List candidates = new ArrayList<>(); for ( SourceMethod oneMethod : rawMethods ) { - if ( method.reverses( oneMethod ) ) { + 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() ) { @@ -571,7 +604,7 @@ 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 ) { @@ -588,27 +621,34 @@ else if ( candidates.size() > 1 ) { resultMethod = nameFilteredcandidates.get( 0 ); } else if ( nameFilteredcandidates.size() > 1 ) { - reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, reversePrism ); + reportErrorWhenSeveralNamesMatch( nameFilteredcandidates, method, inverseConfiguration ); } else { - reportErrorWhenAmbigousReverseMapping( candidates, method, reversePrism ); + reportErrorWhenAmbiguousReverseMapping( candidates, method, inverseConfiguration ); } } } - return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods ); + return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods, + getAnnotationMirror( inverseConfiguration ) ); } - private MappingOptions extractInitializedOptions(SourceMethod resultMethod, + private AnnotationMirror getAnnotationMirror(InheritInverseConfigurationGem inverseConfiguration) { + return inverseConfiguration == null ? null : inverseConfiguration.mirror(); + } + + 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; @@ -620,15 +660,14 @@ private MappingOptions extractInitializedOptions(SourceMethod resultMethod, * source/target type and optionally the name given via {@code @InheritConfiguration}). The method cannot be marked * 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 ) { + if ( inheritConfiguration != null ) { List candidates = new ArrayList<>(); for ( SourceMethod oneMethod : rawMethods ) { @@ -638,7 +677,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 ); @@ -649,46 +688,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 ); } else { - reportErrorWhenAmbigousMapping( candidates, method, forwardPrism ); + reportErrorWhenAmbiguousMapping( candidates, method, inheritConfiguration ); } } } - return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods ); + return extractInitializedOptions( resultMethod, rawMethods, mapperConfig, initializingMethods, + getAnnotationMirror( inheritConfiguration ) ); + } + + 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<>(); 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, "(), " ) @@ -696,7 +740,7 @@ private void reportErrorWhenAmbigousReverseMapping(List candidates } else { messager.printMessage( method.getExecutable(), - reversePrism.mirror, + inverseGem.mirror(), Message.INHERITINVERSECONFIGURATION_INVALID_NAME, Strings.join( candidateNames, "(), " ), name @@ -706,40 +750,40 @@ 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<>(); 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, "(), " ) ); @@ -747,7 +791,7 @@ private void reportErrorWhenAmbigousMapping(List candidates, Sourc else { messager.printMessage( method.getExecutable(), - prism.mirror, + gem.mirror(), Message.INHERITCONFIGURATION_INVALIDNAME, Strings.join( candidateNames, "(), " ), name @@ -756,26 +800,37 @@ private void reportErrorWhenAmbigousMapping(List candidates, Sourc } private void reportErrorWhenSeveralNamesMatch(List candidates, SourceMethod method, - InheritConfigurationPrism prism) { + InheritConfigurationGem gem) { messager.printMessage( method.getExecutable(), - prism.mirror, + gem.mirror(), Message.INHERITCONFIGURATION_DUPLICATE_MATCHES, - prism.name(), + gem.name().get(), Strings.join( candidates, ", " ) ); } private void reportErrorWhenNonMatchingName(SourceMethod onlyCandidate, SourceMethod method, - InheritConfigurationPrism prims) { + InheritConfigurationGem gem) { messager.printMessage( method.getExecutable(), - prims.mirror, + 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/MapperServiceProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperServiceProcessor.java index a2b7cf747e..ee0bf34634 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperServiceProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperServiceProcessor.java @@ -6,16 +6,18 @@ package org.mapstruct.ap.internal.processor; import java.io.IOException; - import javax.annotation.processing.Filer; import javax.lang.model.element.TypeElement; import javax.tools.FileObject; import javax.tools.StandardLocation; +import org.mapstruct.ap.internal.gem.MappingConstantsGem; +import org.mapstruct.ap.internal.model.Decorator; import org.mapstruct.ap.internal.model.GeneratedType; import org.mapstruct.ap.internal.model.Mapper; import org.mapstruct.ap.internal.model.ServicesEntry; -import org.mapstruct.ap.internal.util.MapperConfiguration; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.source.MapperOptions; import org.mapstruct.ap.internal.writer.ModelWriter; /** @@ -37,9 +39,9 @@ public Void process(ProcessorContext context, TypeElement mapperTypeElement, Map } else { String componentModel = - MapperConfiguration.getInstanceOn( mapperTypeElement ).componentModel( context.getOptions() ); + MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() ).componentModel( ); - spiGenerationNeeded = "default".equals( componentModel ); + spiGenerationNeeded = MappingConstantsGem.ComponentModelGem.DEFAULT.equals( componentModel ); } if ( !context.isErroneous() && spiGenerationNeeded && mapper.hasCustomImplementation() ) { @@ -55,15 +57,28 @@ public int getPriority() { private void writeToSourceFile(Filer filer, Mapper model) { ModelWriter modelWriter = new ModelWriter(); - ServicesEntry servicesEntry = getServicesEntry( model.getDecorator() == null ? model : model.getDecorator() ); + ServicesEntry servicesEntry = getServicesEntry( model ); createSourceFile( servicesEntry, modelWriter, filer ); } - private ServicesEntry getServicesEntry(GeneratedType model) { - String mapperName = model.getInterfaceName() != null ? model.getInterfaceName() : model.getSuperClassName(); + private ServicesEntry getServicesEntry(Mapper mapper) { + if ( mapper.getDecorator() != null ) { + return getServicesEntry( mapper.getDecorator() ); + } + + return getServicesEntry( mapper.getMapperDefinitionType(), mapper ); + } + + private ServicesEntry getServicesEntry(Decorator decorator) { + return getServicesEntry( decorator.getMapperType(), decorator ); + } + + private ServicesEntry getServicesEntry(Type mapperType, GeneratedType model) { + String mapperName = mapperType.getName(); + String mapperPackageName = mapperType.getPackageName(); - return new ServicesEntry(model.getInterfacePackage(), mapperName, + return new ServicesEntry(mapperPackageName, mapperName, model.getPackageName(), model.getName()); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index 9ba8d54d54..d7a0329aeb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -7,46 +7,59 @@ import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; 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.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ExecutableType; import javax.lang.model.type.TypeKind; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; +import org.mapstruct.ap.internal.gem.BeanMappingGem; +import org.mapstruct.ap.internal.gem.ConditionGem; +import org.mapstruct.ap.internal.gem.IgnoredGem; +import org.mapstruct.ap.internal.gem.IgnoredListGem; +import org.mapstruct.ap.internal.gem.IterableMappingGem; +import org.mapstruct.ap.internal.gem.MapMappingGem; +import org.mapstruct.ap.internal.gem.MappingGem; +import org.mapstruct.ap.internal.gem.MappingsGem; +import org.mapstruct.ap.internal.gem.ObjectFactoryGem; +import org.mapstruct.ap.internal.gem.SourcePropertyNameGem; +import org.mapstruct.ap.internal.gem.SubclassMappingGem; +import org.mapstruct.ap.internal.gem.SubclassMappingsGem; +import org.mapstruct.ap.internal.gem.TargetPropertyNameGem; +import org.mapstruct.ap.internal.gem.ValueMappingGem; +import org.mapstruct.ap.internal.gem.ValueMappingsGem; 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.BeanMapping; -import org.mapstruct.ap.internal.model.source.IterableMapping; -import org.mapstruct.ap.internal.model.source.MapMapping; -import org.mapstruct.ap.internal.model.source.Mapping; +import org.mapstruct.ap.internal.model.source.BeanMappingOptions; +import org.mapstruct.ap.internal.model.source.ConditionOptions; +import org.mapstruct.ap.internal.model.source.EnumMappingOptions; +import org.mapstruct.ap.internal.model.source.IterableMappingOptions; +import org.mapstruct.ap.internal.model.source.MapMappingOptions; +import org.mapstruct.ap.internal.model.source.MapperOptions; +import org.mapstruct.ap.internal.model.source.MappingOptions; import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods; import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.model.source.ValueMapping; -import org.mapstruct.ap.internal.prism.BeanMappingPrism; -import org.mapstruct.ap.internal.prism.IterableMappingPrism; -import org.mapstruct.ap.internal.prism.MapMappingPrism; -import org.mapstruct.ap.internal.prism.MappingPrism; -import org.mapstruct.ap.internal.prism.MappingsPrism; -import org.mapstruct.ap.internal.prism.ObjectFactoryPrism; -import org.mapstruct.ap.internal.prism.ValueMappingPrism; -import org.mapstruct.ap.internal.prism.ValueMappingsPrism; +import org.mapstruct.ap.internal.model.source.SubclassMappingOptions; +import org.mapstruct.ap.internal.model.source.SubclassValidator; +import org.mapstruct.ap.internal.model.source.ValueMappingOptions; +import org.mapstruct.ap.internal.option.Options; import org.mapstruct.ap.internal.util.AccessorNamingUtils; import org.mapstruct.ap.internal.util.AnnotationProcessingException; +import org.mapstruct.ap.internal.util.ElementUtils; 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.Message; - -import static org.mapstruct.ap.internal.util.Executables.getAllEnclosedExecutableElements; +import org.mapstruct.ap.internal.util.MetaAnnotations; +import org.mapstruct.ap.internal.util.RepeatableAnnotations; +import org.mapstruct.ap.internal.util.TypeUtils; +import org.mapstruct.ap.spi.EnumTransformationStrategy; /** * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s @@ -58,11 +71,22 @@ */ public class MethodRetrievalProcessor 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 AccessorNamingUtils accessorNaming; - private Types typeUtils; - private Elements elementUtils; + private Map enumTransformationStrategies; + private TypeUtils typeUtils; + private ElementUtils elementUtils; + private Options options; @Override public List process(ProcessorContext context, TypeElement mapperTypeElement, Void sourceModel) { @@ -71,24 +95,31 @@ public List process(ProcessorContext context, TypeElement mapperTy 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 ); - MapperConfiguration mapperConfig = MapperConfiguration.getInstanceOn( mapperTypeElement ); + MapperOptions mapperOptions = MapperOptions.getInstanceOn( mapperTypeElement, context.getOptions() ); - if ( mapperConfig != null ) { - this.messager.note( 0, Message.CONFIG_NOTE, mapperConfig.getClass().getName() ); + 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( mapperTypeElement, mapperConfig ); - return retrieveMethods( mapperTypeElement, mapperTypeElement, mapperConfig, prototypeMethods ); + List prototypeMethods = retrievePrototypeMethods( mapperTypeElement, mapperOptions ); + return retrieveMethods( + typeFactory.getType( mapperTypeElement.asType() ), + mapperTypeElement, + mapperOptions, + prototypeMethods + ); } @Override @@ -97,16 +128,21 @@ public int getPriority() { } private List retrievePrototypeMethods(TypeElement mapperTypeElement, - MapperConfiguration mapperConfig) { - if ( mapperConfig.config() == null ) { + MapperOptions mapperAnnotation) { + if ( !mapperAnnotation.hasMapperConfig() ) { return Collections.emptyList(); } - TypeElement typeElement = asTypeElement( mapperConfig.config() ); + TypeElement typeElement = asTypeElement( mapperAnnotation.mapperConfigType() ); List methods = new ArrayList<>(); - for ( ExecutableElement executable : getAllEnclosedExecutableElements( elementUtils, typeElement ) ) { + 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( mapperConfig.config(), executable ); + ExecutableType methodType = typeFactory.getMethodType( mapperAnnotation.mapperConfigType(), executable ); List parameters = typeFactory.getParameters( methodType, executable ); boolean containsTargetTypeParameter = SourceMethod.containsTargetTypeParameter( parameters ); @@ -119,7 +155,7 @@ private List retrievePrototypeMethods(TypeElement mapperTypeElemen executable, parameters, containsTargetTypeParameter, - mapperConfig, + mapperAnnotation, prototypeMethods, mapperTypeElement ); @@ -135,22 +171,24 @@ private List retrievePrototypeMethods(TypeElement mapperTypeElemen /** * 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) { + 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 ) { @@ -160,12 +198,23 @@ 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 ( DeclaredType 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 + ); + } } } @@ -176,13 +225,13 @@ 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( (DeclaredType) usedMapper.asType(), method ); + TypeElement usedMapper = usedMapperType.getTypeElement(); + ExecutableType methodType = typeFactory.getMethodType( (DeclaredType) usedMapperType.getTypeMirror(), method ); List parameters = typeFactory.getParameters( methodType, method ); Type returnType = typeFactory.getReturnType( methodType ); @@ -195,13 +244,14 @@ private SourceMethod getMethod(TypeElement usedMapper, method, parameters, containsTargetTypeParameter, - mapperConfig, + mapperOptions, prototypeMethods, mapperToImplement ); } // otherwise add reference to existing mapper method else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( method, parameters, returnType ) - || isValidLifecycleCallbackMethod( method, returnType ) ) { + || isValidLifecycleCallbackMethod( method ) + || isValidPresenceCheckMethod( method, parameters, returnType ) ) { return getReferencedMethod( usedMapper, methodType, method, mapperToImplement, parameters ); } else { @@ -212,7 +262,7 @@ else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( method, private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, ExecutableElement method, List parameters, boolean containsTargetTypeParameter, - MapperConfiguration mapperConfig, + MapperOptions mapperOptions, List prototypeMethods, TypeElement mapperToImplement) { Type returnType = typeFactory.getReturnType( methodType ); @@ -237,38 +287,76 @@ private SourceMethod getMethodRequiringImplementation(ExecutableType methodType, } ParameterProvidedMethods contextProvidedMethods = - retrieveContextProvidedMethods( contextParameters, mapperToImplement, mapperConfig ); + 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, - typeUtils - ) ) - .setMapMapping( - MapMapping.fromPrism( MapMappingPrism.getInstanceOn( method ), method, messager, typeUtils ) ) - .setBeanMapping( - BeanMapping.fromPrism( BeanMappingPrism.getInstanceOn( method ), method, messager, typeUtils ) ) - .setValueMappings( getValueMappings( method ) ) + .setMapper( mapperOptions ) + .setBeanMappingOptions( beanMappingOptions ) + .setMappingOptions( mappingOptions ) + .setIterableMappingOptions( iterableMappingOptions ) + .setMapMappingOptions( mapMappingOptions ) + .setValueMappingOptionss( getValueMappings( method ) ) + .setEnumMappingOptions( enumMappingOptions ) + .setSubclassMappings( subclassMappingOptions ) + .setSubclassValidator( subclassValidator ) .setTypeUtils( typeUtils ) - .setMessager( messager ) .setTypeFactory( typeFactory ) - .setAccessorNaming( accessorNaming ) - .setMapperConfiguration( mapperConfig ) .setPrototypeMethods( prototypeMethods ) .setContextProvidedMethods( contextProvidedMethods ) + .setVerboseLogging( options.isVerbose() ) .build(); } private ParameterProvidedMethods retrieveContextProvidedMethods( - List contextParameters, TypeElement mapperToImplement, MapperConfiguration mapperConfig) { + List contextParameters, TypeElement mapperToImplement, MapperOptions mapperConfig) { ParameterProvidedMethods.Builder builder = ParameterProvidedMethods.builder(); for ( Parameter contextParam : contextParameters ) { @@ -276,14 +364,15 @@ private ParameterProvidedMethods retrieveContextProvidedMethods( continue; } List contextParamMethods = retrieveMethods( - contextParam.getType().getTypeElement(), + contextParam.getType(), mapperToImplement, mapperConfig, - Collections. emptyList() ); + Collections.emptyList() ); List contextProvidedMethods = new ArrayList<>( contextParamMethods.size() ); for ( SourceMethod sourceMethod : contextParamMethods ) { - if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory() ) { + if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory() + || sourceMethod.getConditionOptions().isAnyStrategyApplicable() ) { contextProvidedMethods.add( sourceMethod ); } } @@ -311,18 +400,19 @@ 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 ) - .setAccessorNaming( accessorNaming ) + .setConditionOptions( getConditionOptions( method, parameters ) ) + .setVerboseLogging( options.isVerbose() ) .build(); } - private boolean isValidLifecycleCallbackMethod(ExecutableElement method, Type returnType) { + private boolean isValidLifecycleCallbackMethod(ExecutableElement method) { return Executables.isLifecycleCallbackMethod( method ); } @@ -336,13 +426,45 @@ private boolean isValidFactoryMethod(ExecutableElement method, List p } private boolean hasFactoryAnnotation(ExecutableElement method) { - return ObjectFactoryPrism.getInstanceOn( method ) != null; + 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; @@ -387,8 +509,7 @@ private Type selectResultType(Type returnType, Parameter targetParameter) { private boolean checkParameterAndReturnType(ExecutableElement method, List sourceParameters, Parameter targetParameter, List contextParameters, - Type resultType, Type returnType, - boolean containsTargetTypeParameter) { + Type resultType, Type returnType, boolean containsTargetTypeParameter) { if ( sourceParameters.isEmpty() ) { messager.printMessage( method, Message.RETRIEVAL_NO_INPUT_ARGS ); return false; @@ -406,8 +527,8 @@ private boolean checkParameterAndReturnType(ExecutableElement method, List> getMappings(ExecutableElement method) { - Map> mappings = new HashMap<>(); + private Set 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 ); + } - MappingPrism mappingAnnotation = MappingPrism.getInstanceOn( method ); - MappingsPrism mappingsAnnotation = MappingsPrism.getInstanceOn( 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 + */ - if ( mappingAnnotation != null ) { - if ( !mappings.containsKey( mappingAnnotation.target() ) ) { - mappings.put( mappingAnnotation.target(), new ArrayList<>() ); - } - Mapping mapping = Mapping.fromMappingPrism( mappingAnnotation, method, messager, typeUtils ); - if ( mapping != null ) { - mappings.get( mappingAnnotation.target() ).add( mapping ); - } + 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 ); } - if ( mappingsAnnotation != null ) { - mappings.putAll( Mapping.fromMappingsPrism( mappingsAnnotation, method, messager, typeUtils ) ); + @Override + protected void addInstance(MappingGem gem, Element method, Set mappings) { + MappingOptions.addInstance( + gem, + (ExecutableElement) method, + beanMappingOptions, + messager, + typeUtils, + mappings ); } - return 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 ); + } } /** @@ -534,23 +777,116 @@ private Map> getMappings(ExecutableElement method) { * * @return The mappings for the given method, keyed by target property name */ - private List getValueMappings(ExecutableElement method) { - List valueMappings = new ArrayList<>(); + private List getValueMappings(ExecutableElement method) { + Set processedAnnotations = new RepeatValueMappings().getProcessedAnnotations( method ); + return new ArrayList<>(processedAnnotations); + } + + private class RepeatValueMappings + extends RepeatableAnnotations { + + 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 { - ValueMappingPrism mappingAnnotation = ValueMappingPrism.getInstanceOn( method ); - ValueMappingsPrism mappingsAnnotation = ValueMappingsPrism.getInstanceOn( method ); + 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 ); + } - if ( mappingAnnotation != null ) { - ValueMapping valueMapping = ValueMapping.fromMappingPrism( mappingAnnotation, method, messager ); - if ( valueMapping != null ) { - valueMappings.add( valueMapping ); + @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; + + protected IgnoredConditions( Set processedAnnotations ) { + super( elementUtils, IGNORED_FQN, IGNORED_LIST_FQN ); + this.processedAnnotations = processedAnnotations; + } + + @Override + protected IgnoredGem singularInstanceOn(Element element) { + return IgnoredGem.instanceOn( element ); + } + + @Override + protected IgnoredListGem multipleInstanceOn(Element element) { + return IgnoredListGem.instanceOn( element ); + } - if ( mappingsAnnotation != null ) { - ValueMapping.fromMappingsPrism( mappingsAnnotation, method, messager, valueMappings ); + @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 ); + } + } } - return valueMappings; + @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 74f0528d6e..4a30b776b7 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 @@ -5,10 +5,11 @@ */ 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 org.mapstruct.ap.internal.util.ElementUtils; +import org.mapstruct.ap.internal.util.TypeUtils; import javax.tools.Diagnostic.Kind; import org.mapstruct.ap.internal.model.common.TypeFactory; @@ -16,6 +17,8 @@ import org.mapstruct.ap.internal.util.AccessorNamingUtils; import org.mapstruct.ap.internal.util.FormattingMessager; 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 @@ -32,18 +35,18 @@ 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(); @@ -51,6 +54,10 @@ public interface ProcessorContext { AccessorNamingUtils getAccessorNaming(); + 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 7780501a78..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 @@ -8,10 +8,24 @@ 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} @@ -22,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() ); + if ( !isAlreadyAnnotatedAsSpringStereotype( mapper ) ) { + typeAnnotations.add( component() ); + } if ( mapper.getDecorator() != null ) { typeAnnotations.add( qualifierDelegate() ); @@ -39,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 @@ -67,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 d9e8748932..67cdc09fc9 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 @@ -5,24 +5,39 @@ */ package org.mapstruct.ap.internal.processor.creation; +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; + 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.ElementFilter; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; import org.mapstruct.ap.internal.conversion.ConversionProvider; import org.mapstruct.ap.internal.conversion.Conversions; +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; @@ -32,6 +47,7 @@ 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; @@ -41,16 +57,16 @@ 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.prism.ReportingPolicyPrism; 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.MessageConstants; import org.mapstruct.ap.internal.util.NativeTypes; import org.mapstruct.ap.internal.util.Strings; - -import static java.util.Collections.singletonList; -import static org.mapstruct.ap.internal.util.Collections.first; +import org.mapstruct.ap.internal.util.TypeUtils; /** * The one and only implementation of {@link MappingResolver}. The class has been split into an interface an @@ -61,8 +77,10 @@ */ 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; @@ -72,15 +90,24 @@ public class MappingResolverImpl implements MappingResolver { 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 usedSupportedMappings = new HashSet<>(); + private final Set usedSupportedMappings = new LinkedHashSet<>(); - public MappingResolverImpl(FormattingMessager messager, Elements elementUtils, Types typeUtils, + /** + * 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) { + List mapperReferences, boolean verboseLogging) { this.messager = messager; this.typeUtils = typeUtils; this.typeFactory = typeFactory; @@ -88,24 +115,32 @@ 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, messager ); + this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager, null ); + + this.verboseLogging = verboseLogging; } @Override - public Assignment getTargetAssignment(Method mappingMethod, Type targetType, + public Assignment getTargetAssignment(Method mappingMethod, ForgedMethodHistory description, Type targetType, FormattingParameters formattingParameters, SelectionCriteria criteria, SourceRHS sourceRHS, - AnnotationMirror positionHint) { + AnnotationMirror positionHint, + Supplier forger) { ResolvingAttempt attempt = new ResolvingAttempt( sourceModel, mappingMethod, + description, formattingParameters, sourceRHS, criteria, - positionHint + positionHint, + forger, + builtInMethods.getBuiltInMethods(), + messager, + verboseLogging ); return attempt.getTargetAssignment( sourceRHS.getSourceTypeForMatching(), targetType ); @@ -116,6 +151,11 @@ public Set getUsedSupportedMappings() { return usedSupportedMappings; } + @Override + public Set getUsedSupportedFields() { + return usedSupportedFields; + } + private MapperReference findMapperReference(Method method) { for ( MapperReference ref : mapperReferences ) { if ( ref.getType().equals( method.getDeclaringMapper() ) ) { @@ -130,38 +170,51 @@ private MapperReference findMapperReference(Method method) { private class ResolvingAttempt { private final Method mappingMethod; + private final ForgedMethodHistory description; private final List methods; private final SelectionCriteria selectionCriteria; private final SourceRHS sourceRHS; - private final boolean savedPreferUpdateMapping; 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 supportingMethodCandidates; - private ResolvingAttempt(List sourceModel, Method mappingMethod, + // CHECKSTYLE:OFF + private ResolvingAttempt(List sourceModel, Method mappingMethod, ForgedMethodHistory description, FormattingParameters formattingParameters, SourceRHS sourceRHS, SelectionCriteria criteria, - AnnotationMirror positionHint) { + AnnotationMirror positionHint, + Supplier forger, + List builtIns, + FormattingMessager messager, boolean verboseLogging) { this.mappingMethod = mappingMethod; - this.methods = filterPossibleCandidateMethods( sourceModel ); + this.description = description; this.formattingParameters = formattingParameters == null ? FormattingParameters.EMPTY : formattingParameters; this.sourceRHS = sourceRHS; - this.supportingMethodCandidates = new HashSet<>(); + 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) { + 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 ); } } @@ -169,19 +222,30 @@ 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( sourceRHS ); - 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 ( !hasQualfiers() ) { - if ( sourceType.isAssignableTo( targetType ) || - isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) { + if ( ( sourceType.isAssignableTo( targetType ) || + isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) + && allowDirect( sourceType, targetType ) ) { Assignment simpleAssignment = sourceRHS; return simpleAssignment; } @@ -200,47 +264,71 @@ private Assignment getTargetAssignment(Type sourceType, Type targetType) { } // then type conversion - if ( !hasQualfiers() ) { - ConversionAssignment conversion = resolveViaConversion( sourceType, targetType ); - if ( conversion != null ) { - conversion.reportMessageWhenNarrowing( messager, this ); - conversion.getAssignment().setAssignment( sourceRHS ); - return conversion.getAssignment(); + 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 + 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; + } } } - // check for a built-in method - if (!hasQualfiers() ) { - Assignment builtInMethod = resolveViaBuiltInMethod( sourceType, targetType ); - if ( builtInMethod != null ) { - builtInMethod.setAssignment( sourceRHS ); + if ( allow2Steps() ) { + // 2 step method, first: method(method(source)) + assignment = MethodMethod.getBestMatch( this, sourceType, targetType ); + if ( assignment != null ) { usedSupportedMappings.addAll( supportingMethodCandidates ); - return builtInMethod; + return assignment; } - } - // 2 step method, first: method(method(source)) - referencedMethod = resolveViaMethodAndMethod( sourceType, targetType ); - if ( referencedMethod != null ) { - usedSupportedMappings.addAll( supportingMethodCandidates ); - 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; + } + } - // 2 step method, then: method(conversion(source)) - referencedMethod = resolveViaConversionAndMethod( sourceType, targetType ); - if ( referencedMethod != null ) { - usedSupportedMappings.addAll( supportingMethodCandidates ); - return referencedMethod; - } + // 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)) + if ( allowConversion() ) { + assignment = MethodConversion.getBestMatch( this, sourceType, targetType ); + if ( assignment != null ) { + usedSupportedMappings.addAll( supportingMethodCandidates ); + return assignment; + } + } + } - // 2 step method, finally: conversion(method(source)) - ConversionAssignment conversion = resolveViaMethodAndConversion( sourceType, targetType ); - if ( conversion != null ) { - usedSupportedMappings.addAll( supportingMethodCandidates ); - return conversion.getAssignment(); + 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 @@ -251,231 +339,141 @@ private boolean hasQualfiers() { return selectionCriteria != null && selectionCriteria.hasQualfiers(); } - private ConversionAssignment resolveViaConversion(Type sourceType, Type targetType) { - - ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); - - 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 ) + ); } - ConversionContext ctx = new DefaultConversionContext( - typeFactory, - messager, - sourceType, - targetType, - formattingParameters - ); - - // add helper methods required in conversion - for ( HelperMethod helperMethod : conversionProvider.getRequiredHelperMethods( ctx ) ) { - usedSupportedMappings.add( new SupportingMappingMethod( helperMethod ) ); + else if ( !annotations.isEmpty() ) { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_NO_QUALIFYING_METHOD_ANNOTATION, + Strings.join( annotations, MessageConstants.AND ) + ); } - - Assignment conversion = conversionProvider.to( ctx ); - if ( conversion != null ) { - return new ConversionAssignment( sourceType, targetType, conversionProvider.to( ctx ) ); + else { + messager.printMessage( + mappingMethod.getExecutable(), + positionHint, + Message.GENERAL_NO_QUALIFYING_METHOD_NAMED, + Strings.join( names, MessageConstants.AND ) + ); } - return null; + } - /** - * 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 - SelectedMethod 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; - } - - private Assignment resolveViaBuiltInMethod(Type sourceType, Type targetType) { - SelectedMethod matchingBuiltInMethod = - getBestMatch( builtInMethods.getBuiltInMethods(), sourceType, targetType ); + if ( type.isArrayType() ) { + return type.isJavaLangType() || type.getComponentType().isPrimitive(); + } - if ( matchingBuiltInMethod != null ) { + 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 ) ); + } - Set allUsedFields = new HashSet<>( mapperReferences ); - SupportingField.addAllFieldsIn( supportingMethodCandidates, allUsedFields ); - SupportingMappingMethod supportingMappingMethod = - new SupportingMappingMethod( matchingBuiltInMethod.getMethod(), allUsedFields ); - supportingMethodCandidates.add( supportingMappingMethod ); - ConversionContext ctx = new DefaultConversionContext( - typeFactory, - messager, - sourceType, - targetType, - formattingParameters - ); - Assignment methodReference = MethodReference.forBuiltInMethod( matchingBuiltInMethod.getMethod(), ctx ); - methodReference.setAssignment( sourceRHS ); - 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 ) { - if ( Object.class.getName() - .equals( methodYCandidate.getSourceParameters().get( 0 ).getType().getName() ) ) { - // java.lang.Object as intermediate result - continue; - } - - 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( sourceRHS ); - break; - } - else { - // both should match; - supportingMethodCandidates.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 ) ) - * - * In stead 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 Assignment resolveViaConversionAndMethod(Type sourceType, Type targetType) { - - List methodYCandidates = new ArrayList<>( methods ); - methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); - - Assignment methodRefY = null; + private boolean allowMappingMethod() { + return selectionCriteria != null && selectionCriteria.isAllowMappingMethod(); + } - for ( Method methodYCandidate : methodYCandidates ) { - if ( Object.class.getName() - .equals( methodYCandidate.getSourceParameters().get( 0 ).getType().getName() ) ) { - // java.lang.Object as intermediate result - continue; - } + private boolean allow2Steps() { + return selectionCriteria != null && selectionCriteria.isAllow2Steps(); + } - methodRefY = - resolveViaMethod( methodYCandidate.getSourceParameters().get( 0 ).getType(), targetType, true ); + private ConversionAssignment resolveViaConversion(Type sourceType, Type targetType) { - if ( methodRefY != null ) { - Type targetTypeX = methodYCandidate.getSourceParameters().get( 0 ).getType(); - ConversionAssignment conversionXRef = resolveViaConversion( sourceType, targetTypeX ); - if ( conversionXRef != null ) { - methodRefY.setAssignment( conversionXRef.getAssignment() ); - conversionXRef.getAssignment().setAssignment( sourceRHS ); - conversionXRef.reportMessageWhenNarrowing( messager, this ); - break; - } - else { - // both should match - supportingMethodCandidates.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 conversionY( methodX ( parameter ) ) - * - * In stead 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. - */ - private ConversionAssignment 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 ); - ConversionAssignment 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.isUpdateMethod() || - Object.class.getName().equals( methodXCandidate.getReturnType().getFullyQualifiedName() ) ) { - // skip update methods || java.lang.Object as intermediate result - 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.getAssignment().setAssignment( methodRefX ); - methodRefX.setAssignment( sourceRHS ); - conversionYRef.reportMessageWhenNarrowing( messager, this ); - break; - } - else { - // both should match; - supportingMethodCandidates.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 ); } @@ -494,59 +492,77 @@ private boolean isUpdateMethodForMapping(Method methodCandidate) { && !methodCandidate.isLifecycleCallbackMethod(); } - private SelectedMethod 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, - singletonList( 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 ) { + String descriptionStr = ""; + if ( description != null ) { + descriptionStr = description.createSourcePropertyErrorMessage(); + } + else { + descriptionStr = sourceRHS.getSourceErrorMessagePart(); + } + if ( sourceRHS.getSourceErrorMessagePart() != null ) { messager.printMessage( mappingMethod.getExecutable(), positionHint, - Message.GENERAL_AMBIGIOUS_MAPPING_METHOD, - sourceRHS.getSourceErrorMessagePart(), - returnType, - Strings.join( candidates, ", " ) + Message.GENERAL_AMBIGUOUS_MAPPING_METHOD, + descriptionStr, + target.describe(), + join( candidates ) ); } else { messager.printMessage( mappingMethod.getExecutable(), positionHint, - Message.GENERAL_AMBIGIOUS_FACTORY_METHOD, - returnType, - Strings.join( candidates, ", " ) + Message.GENERAL_AMBIGUOUS_FACTORY_METHOD, + target.describe(), + join( candidates ) ); } } - - if ( !candidates.isEmpty() ) { - return first( candidates ); - } - - return null; } - private Assignment getMappingMethodReference(SelectedMethod method, - Type targetType) { - MapperReference mapperReference = findMapperReference( method.getMethod() ); + private Assignment toMethodRef(SelectedMethod selectedMethod) { + MapperReference mapperReference = findMapperReference( selectedMethod.getMethod() ); return MethodReference.forMapperReference( - method.getMethod(), + selectedMethod.getMethod(), mapperReference, - method.getParameterBindings() + selectedMethod.getParameterBindings() ); } + 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 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. @@ -625,6 +641,19 @@ private boolean hasCompatibleCopyConstructor(Type sourceType, Type targetType) { return false; } + + private String join( List> candidates ) { + + String candidateStr = candidates.stream() + .limit( reportingLimitAmbiguous ) + .map( m -> m.getMethod().describe() ) + .collect( Collectors.joining( ", " ) ); + + if ( candidates.size() > reportingLimitAmbiguous ) { + candidateStr += String.format( "... and %s more", candidates.size() - reportingLimitAmbiguous ); + } + return candidateStr; + } } private static class ConversionAssignment { @@ -645,12 +674,21 @@ Assignment getAssignment() { void reportMessageWhenNarrowing(FormattingMessager messager, ResolvingAttempt attempt) { - if ( NativeTypes.isNarrowing( sourceType.getFullyQualifiedName(), targetType.getFullyQualifiedName() ) ) { - ReportingPolicyPrism policy = attempt.mappingMethod.getMapperConfiguration().typeConversionPolicy(); - if ( policy == ReportingPolicyPrism.WARN ) { + Type source = sourceType; + if ( sourceType.isOptionalType() ) { + source = sourceType.getOptionalBaseType(); + } + + Type target = targetType; + if ( targetType.isOptionalType() ) { + target = targetType.getOptionalBaseType(); + } + 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 == ReportingPolicyPrism.ERROR ) { + else if ( policy == ReportingPolicyGem.ERROR ) { report( messager, attempt, Message.CONVERSION_LOSSY_ERROR ); } } @@ -663,10 +701,446 @@ private void report(FormattingMessager messager, ResolvingAttempt attempt, Messa attempt.positionHint, message, attempt.sourceRHS.getSourceErrorMessagePart(), - sourceType.toString(), - targetType.toString() + sourceType.describe(), + targetType.describe() ); } + 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/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 index 3d0d11686b..8025acfeb1 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AccessorNamingUtils.java @@ -10,11 +10,11 @@ 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 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.ExecutableElementAccessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.MethodType; @@ -33,46 +33,38 @@ public AccessorNamingUtils(AccessorNamingStrategy accessorNamingStrategy) { this.accessorNamingStrategy = accessorNamingStrategy; } - public boolean isGetterMethod(Accessor method) { - ExecutableElement executable = method.getExecutable(); - return executable != null && isPublicNotStatic( method ) && + public boolean isGetterMethod(ExecutableElement executable) { + return executable != null && isPublicNotStatic( executable ) && executable.getParameters().isEmpty() && accessorNamingStrategy.getMethodType( executable ) == MethodType.GETTER; } - public boolean isPresenceCheckMethod(Accessor method) { - if ( !( method instanceof ExecutableElementAccessor ) ) { - return false; - } - ExecutableElement executable = method.getExecutable(); + public boolean isPresenceCheckMethod(ExecutableElement executable) { + return executable != null - && isPublicNotStatic( method ) + && 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(Accessor method) { - ExecutableElement executable = method.getExecutable(); + public boolean isSetterMethod(ExecutableElement executable) { return executable != null - && isPublicNotStatic( method ) + && isPublicNotStatic( executable ) && executable.getParameters().size() == 1 && accessorNamingStrategy.getMethodType( executable ) == MethodType.SETTER; } - public boolean isAdderMethod(Accessor method) { - ExecutableElement executable = method.getExecutable(); + public boolean isAdderMethod(ExecutableElement executable) { return executable != null - && isPublicNotStatic( method ) + && isPublicNotStatic( executable ) && executable.getParameters().size() == 1 && accessorNamingStrategy.getMethodType( executable ) == MethodType.ADDER; } - public String getPropertyName(Accessor accessor) { - ExecutableElement executable = accessor.getExecutable(); - return executable != null ? accessorNamingStrategy.getPropertyName( executable ) : - accessor.getSimpleName().toString(); + public String getPropertyName(ExecutableElement executable) { + return accessorNamingStrategy.getPropertyName( executable ); } /** @@ -82,13 +74,17 @@ public String getPropertyName(Accessor accessor) { * {@code addChild(Child v)}, the element name would be 'Child'. */ public String getElementNameForAdder(Accessor adderMethod) { - ExecutableElement executable = adderMethod.getExecutable(); - return executable != null ? accessorNamingStrategy.getElementName( executable ) : null; + 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 SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; @@ -102,7 +98,7 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { } TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; 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 index ae8cc62fbb..81640848d0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/AnnotationProcessorContext.java @@ -5,9 +5,13 @@ */ 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; @@ -20,10 +24,14 @@ 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"). @@ -36,21 +44,29 @@ public class AnnotationProcessorContext implements MapStructProcessingEnvironmen private BuilderProvider builderProvider; private AccessorNamingStrategy accessorNamingStrategy; + private EnumMappingStrategy enumMappingStrategy; private boolean initialized; + private Map enumTransformationStrategies; private AccessorNamingUtils accessorNaming; private Elements elementUtils; private Types typeUtils; private Messager messager; + private boolean disableBuilder; private boolean verbose; - public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean verbose) { + private Map options; + + public AnnotationProcessorContext(Elements elementUtils, Types typeUtils, Messager messager, boolean disableBuilder, + boolean verbose, Map options) { astModifyingAnnotationProcessors = java.util.Collections.unmodifiableList( - findAstModifyingAnnotationProcessors() ); + findAstModifyingAnnotationProcessors( messager ) ); this.elementUtils = elementUtils; this.typeUtils = typeUtils; this.messager = messager; + this.disableBuilder = disableBuilder; this.verbose = verbose; + this.options = java.util.Collections.unmodifiableMap( options ); } /** @@ -94,7 +110,9 @@ else if ( elementUtils.getTypeElement( FreeBuilderConstants.FREE_BUILDER_FQN ) ! + this.accessorNamingStrategy.getClass().getCanonicalName() ); } - this.builderProvider = Services.get( BuilderProvider.class, defaultBuilderProvider ); + this.builderProvider = this.disableBuilder ? + new NoOpBuilderProvider() : + Services.get( BuilderProvider.class, defaultBuilderProvider ); this.builderProvider.init( this ); if ( verbose ) { messager.printMessage( @@ -103,23 +121,120 @@ else if ( elementUtils.getTypeElement( FreeBuilderConstants.FREE_BUILDER_FQN ) ! ); } this.accessorNaming = new AccessorNamingUtils( this.accessorNamingStrategy ); + + 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() { + private static List findAstModifyingAnnotationProcessors(Messager messager) { List processors = new ArrayList<>(); ServiceLoader loader = ServiceLoader.load( AstModifyingAnnotationProcessor.class, AnnotationProcessorContext.class.getClassLoader() ); - for ( Iterator it = loader.iterator(); it.hasNext(); ) { - processors.add( it.next() ); + // 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; @@ -144,8 +259,22 @@ public AccessorNamingStrategy getAccessorNamingStrategy() { 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/ClassUtils.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ClassUtils.java deleted file mode 100644 index 889b729318..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/ClassUtils.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 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; - -/** - * Utilities for working with classes. It is mainly needed because using the {@link javax.lang.model.util.Elements} - * is not always correct. For example when compiling with JDK 9 and source version 8 classes from different modules - * are available by {@link javax.lang.model.util.Elements#getTypeElement(CharSequence)} but they are actually not - * if those modules are not added during compilation. - * - * @author Filip Hrisafov - */ -class ClassUtils { - - private ClassUtils() { - } - - /** - * Determine whether the {@link Class} identified by the supplied name is present - * and can be loaded. Will return {@code false} if either the class or - * one of its dependencies is not present or cannot be loaded. - * - * @param className the name of the class to check - * @param classLoader the class loader to use - * (may be {@code null}, which indicates the default class loader) - * - * @return whether the specified class is present - */ - static boolean isPresent(String className, ClassLoader classLoader) { - try { - ClassLoader classLoaderToUse = classLoader; - if ( classLoaderToUse == null ) { - classLoaderToUse = getDefaultClassLoader(); - } - classLoaderToUse.loadClass( className ); - return true; - } - catch ( ClassNotFoundException ex ) { - // Class or one of its dependencies is not present... - return false; - } - } - - /** - * Return the default ClassLoader to use: typically the thread context - * ClassLoader, if available; the ClassLoader that loaded the ClassUtils - * class will be used as fallback. - *

      Call this method if you intend to use the thread context ClassLoader - * in a scenario where you absolutely need a non-null ClassLoader reference: - * for example, for class path resource loading (but not necessarily for - * {@code Class.forName}, which accepts a {@code null} ClassLoader - * reference as well). - * - * @return the default ClassLoader (never {@code null}) - * - * @see Thread#getContextClassLoader() - */ - private static ClassLoader getDefaultClassLoader() { - ClassLoader cl = null; - try { - cl = Thread.currentThread().getContextClassLoader(); - } - catch ( Throwable ex ) { - // Cannot access thread context ClassLoader - falling back to system class loader... - } - if ( cl == null ) { - // No thread context class loader -> use class loader of this class. - cl = ClassUtils.class.getClassLoader(); - } - return cl; - } - -} 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 7f2d5cd8db..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 @@ -9,6 +9,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -63,4 +64,16 @@ public static List join(List a, List b) { return result; } + 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 6c16b6a8a0..1592a39fda 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 @@ -5,31 +5,14 @@ */ package org.mapstruct.ap.internal.util; -import static javax.lang.model.util.ElementFilter.fieldsIn; -import static javax.lang.model.util.ElementFilter.methodsIn; -import static org.mapstruct.ap.internal.util.workarounds.SpecificCompilerWorkarounds.replaceTypeElementIfNecessary; - import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.ListIterator; - import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -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.internal.prism.AfterMappingPrism; -import org.mapstruct.ap.internal.prism.BeforeMappingPrism; +import org.mapstruct.ap.internal.gem.AfterMappingGem; +import org.mapstruct.ap.internal.gem.BeforeMappingGem; import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.ExecutableElementAccessor; -import org.mapstruct.ap.internal.util.accessor.VariableElementAccessor; -import org.mapstruct.ap.spi.TypeHierarchyErroneousException; /** * Provides functionality around {@link ExecutableElement}s. @@ -54,29 +37,16 @@ public class Executables { private Executables() { } - /** - * An {@link Accessor} is a field accessor, if it doesn't have an executable element, is public and it is not - * static. - * - * @param accessor the accessor to ber checked - * - * @return {@code true} if the {@code accessor} is for a {@code public} non {@code static} field. - */ - public static boolean isFieldAccessor(Accessor accessor) { - ExecutableElement executable = accessor.getExecutable(); - return executable == null && isPublic( accessor ) && isNotStatic( accessor ); - } - - static boolean isPublicNotStatic(Accessor accessor) { - return isPublic( accessor ) && isNotStatic( accessor ); + static boolean isPublicNotStatic(ExecutableElement method) { + return isPublic( method ) && isNotStatic( method ); } - static boolean isPublic(Accessor method) { + static boolean isPublic(ExecutableElement method) { return method.getModifiers().contains( Modifier.PUBLIC ); } - private static boolean isNotStatic(Accessor accessor) { - return !accessor.getModifiers().contains( Modifier.STATIC ); + private static boolean isNotStatic(ExecutableElement method) { + return !method.getModifiers().contains( Modifier.STATIC ); } public static boolean isFinal(Accessor accessor) { @@ -92,173 +62,6 @@ public static boolean isDefaultMethod(ExecutableElement method) { } } - /** - * @param mirror the type positionHint - * - * @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 executables = new ArrayList<>(); - for ( Accessor accessor : getAllEnclosedAccessors( elementUtils, element ) ) { - if ( accessor.getExecutable() != null ) { - executables.add( accessor.getExecutable() ); - } - } - return executables; - } - - /** - * Finds all executable elements/variable elements within the given type element, including executable/variable - * elements defined in super classes and implemented interfaces and including the fields in the . 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 getAllEnclosedAccessors(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 ); - } - - if ( element.asType().getKind() == TypeKind.ERROR ) { - throw new TypeHierarchyErroneousException( element ); - } - - addNotYetOverridden( elementUtils, alreadyAdded, methodsIn( element.getEnclosedElements() ), parentType ); - addFields( alreadyAdded, fieldsIn( element.getEnclosedElements() ) ); - - 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( new ExecutableElementAccessor( toAdd ) ); - } - } - - alreadyCollected.addAll( 0, safeToAdd ); - } - - private static void addFields(List alreadyCollected, List variablesToAdd) { - List safeToAdd = new ArrayList<>( variablesToAdd.size() ); - for ( VariableElement toAdd : variablesToAdd ) { - safeToAdd.add( new VariableElementAccessor( 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 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 static boolean wasNotYetOverridden(Elements elementUtils, List alreadyCollected, - ExecutableElement executable, TypeElement parentType) { - for ( ListIterator it = alreadyCollected.listIterator(); it.hasNext(); ) { - ExecutableElement executableInSubtype = it.next().getExecutable(); - if ( executableInSubtype == null ) { - continue; - } - if ( elementUtils.overrides( executableInSubtype, executable, parentType ) ) { - return false; - } - else if ( elementUtils.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; - } - - /** - * @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) { - 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 executableElement the element to check * @return {@code true}, if the executable element is a method annotated with {@code @BeforeMapping} or @@ -273,7 +76,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; } /** @@ -281,6 +84,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/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 2b784785a5..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 @@ -5,81 +5,141 @@ */ 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.ExecutableElementAccessor; +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(AccessorNamingUtils accessorNaming, List elements) { - List getterMethods = new LinkedList<>(); + private final AccessorNamingUtils accessorNaming; + private final TypeUtils typeUtils; + private final TypeMirror typeMirror; - for ( Accessor method : elements ) { - if ( accessorNaming.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 fieldsIn(List accessors) { - List fieldAccessors = new LinkedList<>(); + @SuppressWarnings("unchecked") + public List recordComponentsIn(TypeElement typeElement) { + if ( RECORD_COMPONENTS_METHOD == null ) { + return java.util.Collections.emptyList(); + } - for ( Accessor accessor : accessors ) { - if ( Executables.isFieldAccessor( accessor ) ) { - fieldAccessors.add( accessor ); - } + try { + return (List) RECORD_COMPONENTS_METHOD.invoke( typeElement ); + } + catch ( IllegalAccessException | InvocationTargetException e ) { + return java.util.Collections.emptyList(); } + } - return fieldAccessors; + 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; } - public static List presenceCheckMethodsIn(AccessorNamingUtils accessorNaming, - List elements) { - List presenceCheckMethods = new LinkedList<>(); + private TypeMirror getReturnType(ExecutableElement executableElement) { + return getWithinContext( executableElement ).getReturnType(); + } - for ( Accessor method : elements ) { - if ( accessorNaming.isPresenceCheckMethod( method ) ) { - presenceCheckMethods.add( (ExecutableElementAccessor) method ); - } - } + 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 ) ); + } - return presenceCheckMethods; + public List presenceCheckMethodsIn(List elements) { + return elements.stream() + .filter( accessorNaming::isPresenceCheckMethod ) + .collect( Collectors.toCollection( LinkedList::new ) ); } - public static List setterMethodsIn(AccessorNamingUtils accessorNaming, List elements) { - List setterMethods = new LinkedList<>(); + public List setterMethodsIn(List elements) { + return elements.stream() + .filter( accessorNaming::isSetterMethod ) + .map( method -> new ElementAccessor( method, getFirstParameter( method ), SETTER ) ) + .collect( Collectors.toCollection( LinkedList::new ) ); + } - for ( Accessor method : elements ) { - if ( accessorNaming.isSetterMethod( method ) ) { - setterMethods.add( method ); - } - } - return setterMethods; + private TypeMirror getFirstParameter(ExecutableElement executableElement) { + return first( getWithinContext( executableElement ).getParameterTypes() ); } - public static List adderMethodsIn(AccessorNamingUtils accessorNaming, List elements) { - List adderMethods = new LinkedList<>(); + private ExecutableType getWithinContext( ExecutableElement executableElement ) { + return (ExecutableType) typeUtils.asMemberOf( (DeclaredType) typeMirror, executableElement ); + } - for ( Accessor method : elements ) { - if ( accessorNaming.isAdderMethod( method ) ) { - adderMethods.add( method ); - } - } + 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 2e4d287d4b..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 @@ -75,4 +75,6 @@ void printMessage(Element e, * @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/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/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 index f23e89435a..acd63abcd7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/JavaStreamConstants.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaStreamConstants.java @@ -13,10 +13,6 @@ public final class JavaStreamConstants { public static final String STREAM_FQN = "java.util.stream.Stream"; - public static final String COLLECTORS_FQN = "java.util.stream.Collectors"; - public static final String STREAM_SUPPORT_FQN = "java.util.stream.StreamSupport"; - public static final String OPTIONAL_FQN = "java.util.Optional"; - public static final String FUNCTION_FQN = "java.util.function.Function"; private JavaStreamConstants() { } 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 f8f01cd18c..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 @@ -10,19 +10,10 @@ */ public final class JaxbConstants { - public static final String JAXB_ELEMENT_FQN = "javax.xml.bind.JAXBElement"; - private static final boolean IS_JAXB_ELEMENT_PRESENT = ClassUtils.isPresent( - JAXB_ELEMENT_FQN, - JaxbConstants.class.getClassLoader() - ); + 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() { } - /** - * @return {@code true} if {@link javax.xml.bind.JAXBElement} is present, {@code false} otherwise - */ - public static boolean isJaxbElementPresent() { - return IS_JAXB_ELEMENT_PRESENT; - } } 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 cb812b53b7..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/MapperConfiguration.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 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.LinkedHashSet; -import java.util.List; -import java.util.Set; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; - -import org.mapstruct.ap.internal.option.Options; -import org.mapstruct.ap.internal.prism.BuilderPrism; -import org.mapstruct.ap.internal.prism.CollectionMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.InjectionStrategyPrism; -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.NullValueCheckStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValueMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.NullValuePropertyMappingStrategyPrism; -import org.mapstruct.ap.internal.prism.ReportingPolicyPrism; - -/** - * 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; - private final DeclaredType config; - - public static MapperConfiguration getInstanceOn(Element e) { - return new MapperConfiguration( MapperPrism.getInstanceOn( e ) ); - } - - private MapperConfiguration(MapperPrism mapperPrism) { - this.mapperPrism = mapperPrism; - - if ( mapperPrism.values.config() != null ) { - // TODO #737 Only a declared type makes sense here; Validate and raise graceful error; - // Also validate that @MapperConfig is present - this.config = (DeclaredType) mapperPrism.config(); - this.mapperConfigPrism = MapperConfigPrism.getInstanceOn( config.asElement() ); - } - else { - this.config = null; - 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 Set uses() { - Set uses = new LinkedHashSet<>(); - - for ( TypeMirror usedMapperType : mapperPrism.uses() ) { - // TODO #737 Only declared type make sense here; Validate and raise graceful error; - uses.add( (DeclaredType) usedMapperType ); - } - - if ( mapperConfigPrism != null ) { - for ( TypeMirror usedMapperType : mapperConfigPrism.uses() ) { - // TODO #737 Only declared type make sense here; Validate and raise graceful error; - uses.add( (DeclaredType) usedMapperType ); - } - } - - return uses; - } - - public List imports() { - List imports = new ArrayList<>(); - imports.addAll( mapperPrism.imports() ); - if ( mapperConfigPrism != null ) { - imports.addAll( mapperConfigPrism.imports() ); - } - return imports; - } - - public ReportingPolicyPrism unmappedTargetPolicy(Options options) { - if ( mapperPrism.values.unmappedTargetPolicy() != null ) { - return ReportingPolicyPrism.valueOf( mapperPrism.unmappedTargetPolicy() ); - } - - if ( mapperConfigPrism != null && mapperConfigPrism.values.unmappedTargetPolicy() != null ) { - return ReportingPolicyPrism.valueOf( mapperConfigPrism.unmappedTargetPolicy() ); - } - - if ( options.getUnmappedTargetPolicy() != null ) { - return options.getUnmappedTargetPolicy(); - } - - // fall back to default defined in the annotation - return ReportingPolicyPrism.valueOf( mapperPrism.unmappedTargetPolicy() ); - } - - public ReportingPolicyPrism unmappedSourcePolicy() { - if ( mapperPrism.values.unmappedSourcePolicy() != null ) { - return ReportingPolicyPrism.valueOf( mapperPrism.unmappedSourcePolicy() ); - } - - if ( mapperConfigPrism != null && mapperConfigPrism.values.unmappedSourcePolicy() != null ) { - return ReportingPolicyPrism.valueOf( mapperConfigPrism.unmappedSourcePolicy() ); - } - - // fall back to default defined in the annotation - return ReportingPolicyPrism.valueOf( mapperPrism.unmappedSourcePolicy() ); - } - - public ReportingPolicyPrism typeConversionPolicy() { - if ( mapperPrism.values.typeConversionPolicy() != null ) { - return ReportingPolicyPrism.valueOf( mapperPrism.typeConversionPolicy() ); - } - - if ( mapperConfigPrism != null && mapperConfigPrism.values.typeConversionPolicy() != null ) { - return ReportingPolicyPrism.valueOf( mapperConfigPrism.typeConversionPolicy() ); - } - - // fall back to default defined in the annotation - return ReportingPolicyPrism.valueOf( mapperPrism.typeConversionPolicy() ); - } - - 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 NullValueCheckStrategyPrism getNullValueCheckStrategy(NullValueCheckStrategyPrism beanPrism, - NullValueCheckStrategyPrism mappingPrism) { - if ( mappingPrism != null ) { - return mappingPrism; - } - else if ( beanPrism != null ) { - return beanPrism; - } - else if ( mapperConfigPrism != null && mapperPrism.values.nullValueCheckStrategy() == null ) { - return NullValueCheckStrategyPrism.valueOf( mapperConfigPrism.nullValueCheckStrategy() ); - } - else { - return NullValueCheckStrategyPrism.valueOf( mapperPrism.nullValueCheckStrategy() ); - } - } - - public NullValuePropertyMappingStrategyPrism getNullValuePropertyMappingStrategy( - NullValuePropertyMappingStrategyPrism beanPrism, - NullValuePropertyMappingStrategyPrism mappingPrism) { - if ( mappingPrism != null ) { - return mappingPrism; - } - else if ( beanPrism != null ) { - return beanPrism; - } - else if ( mapperConfigPrism != null && mapperPrism.values.nullValueCheckStrategy() == null ) { - return NullValuePropertyMappingStrategyPrism.valueOf( - mapperConfigPrism.nullValuePropertyMappingStrategy() - ); - } - else { - return NullValuePropertyMappingStrategyPrism.valueOf( mapperPrism.nullValuePropertyMappingStrategy() ); - } - } - - public InjectionStrategyPrism getInjectionStrategy() { - if ( mapperConfigPrism != null && mapperPrism.values.injectionStrategy() == null ) { - return InjectionStrategyPrism.valueOf( mapperConfigPrism.injectionStrategy() ); - } - else { - return InjectionStrategyPrism.valueOf( mapperPrism.injectionStrategy() ); - } - } - - public NullValueMappingStrategyPrism getNullValueMappingStrategy() { - if ( mapperConfigPrism != null && mapperPrism.values.nullValueMappingStrategy() == null ) { - return NullValueMappingStrategyPrism.valueOf( mapperConfigPrism.nullValueMappingStrategy() ); - } - else { - return NullValueMappingStrategyPrism.valueOf( mapperPrism.nullValueMappingStrategy() ); - } - } - - 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(Options options) { - if ( mapperPrism.values.componentModel() != null ) { - return mapperPrism.componentModel(); - } - - if ( mapperConfigPrism != null && mapperConfigPrism.values.componentModel() != null ) { - return mapperConfigPrism.componentModel(); - } - - if ( options.getDefaultComponentModel() != null ) { - return options.getDefaultComponentModel(); - } - - return mapperPrism.componentModel(); // fall back to default defined in the annotation - } - - public boolean isDisableSubMappingMethodsGeneration() { - if ( mapperPrism.disableSubMappingMethodsGeneration() ) { - return mapperPrism.disableSubMappingMethodsGeneration(); - } - - if ( mapperConfigPrism != null && mapperConfigPrism.disableSubMappingMethodsGeneration() ) { - return mapperConfigPrism.disableSubMappingMethodsGeneration(); - } - - return mapperPrism.disableSubMappingMethodsGeneration(); // fall back to default defined in the annotation - } - - public BuilderPrism getBuilderPrism() { - if ( mapperPrism.values.builder() != null ) { - return mapperPrism.builder(); - } - else if ( mapperConfigPrism != null && mapperConfigPrism.values.builder() != null ) { - return mapperConfigPrism.builder(); - } - else { - return null; - } - } - - public DeclaredType config() { - return config; - } - - public boolean isValid() { - return mapperPrism.isValid; - } - - 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 a7e6159bb1..35bbe846c0 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 @@ -7,6 +7,9 @@ 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. * @@ -17,13 +20,17 @@ public enum Message { // CHECKSTYLE:OFF 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_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 ), @@ -32,8 +39,19 @@ public enum Message { 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 ), @@ -50,28 +68,40 @@ public enum Message { 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_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). 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_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 ), 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_WITH_DETAILS( "Can't map \"%s %s\" to \"%s %s\". Reason: %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\"." ), @@ -96,23 +126,47 @@ public enum Message { 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_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_DEPRECATED( "Mapping of Enums via @Mapping is going to be removed in future versions of MapStruct. Please use @ValueMapping instead!", Diagnostic.Kind.WARNING ), + 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_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_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_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 parameterless constructor." ), + 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 ), @@ -121,19 +175,23 @@ public enum Message { 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." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), INHERITINVERSECONFIGURATION_INVALID_NAME( "None of the candidates %s() matches given name: \"%s\"." ), @@ -150,9 +208,30 @@ public enum Message { 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." ), - VALUE_MAPPING_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_NON_EXISTING_CONSTANT( "Constant %s doesn't exist in enum type %s." ); + 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; 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 769f7b83f0..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 @@ -174,7 +174,7 @@ void removeAndValidateFloatingPointLiteralSuffix() { boolean endsWithDSuffix = PTRN_DOUBLE.matcher( val ).find(); // error handling if ( isFloat && endsWithDSuffix ) { - throw new NumberFormatException( "Assiging double to a float" ); + throw new NumberFormatException( "Assigning double to a float" ); } // remove suffix if ( endsWithLSuffix || endsWithFSuffix || endsWithDSuffix ) { @@ -270,11 +270,11 @@ public void validate(String s) { @Override void parse(String val, int radix) { - Double d = Double.parseDouble( radix == 16 ? "0x" + val : val ); + double d = Double.parseDouble( radix == 16 ? "0x" + val : val ); if ( doubleHasBecomeZero( d ) ) { throw new NumberFormatException( "floating point number too small" ); } - if ( d.isInfinite() ) { + if ( Double.isInfinite( d ) ) { throw new NumberFormatException( "infinitive is not allowed" ); } } @@ -284,7 +284,7 @@ void parse(String val, int radix) { @Override public Class getLiteral() { - return float.class; + return double.class; } } @@ -297,11 +297,11 @@ 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 ); + float f = Float.parseFloat( radix == 16 ? "0x" + val : val ); if ( doubleHasBecomeZero( f ) ) { throw new NumberFormatException( "floating point number too small" ); } - if ( f.isInfinite() ) { + if ( Float.isInfinite( f ) ) { throw new NumberFormatException( "infinitive is not allowed" ); } } @@ -363,7 +363,7 @@ else if ( new BigInteger( val, radix ).bitLength() > 64 ) { @Override public Class getLiteral() { - return int.class; + return long.class; } } @@ -474,6 +474,7 @@ private NativeTypes() { 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 ); } 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 index 78e61fbf29..e34d013bc5 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/RoundContext.java @@ -41,7 +41,7 @@ public void addTypeReadyForProcessing(TypeMirror 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) an no processors have signaled + * 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 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 index 2d37c46db8..292512ff80 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Services.java @@ -18,6 +18,10 @@ 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(); 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 7fbca6dc44..88e0bdf6f6 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 @@ -73,6 +73,8 @@ public class Strings { "while" ); + private static final char UNDERSCORE = '_'; + private Strings() { } @@ -127,6 +129,10 @@ public static boolean isEmpty(String string) { return string == null || string.isEmpty(); } + public static boolean isNotEmpty(String string) { + return !isEmpty( string ); + } + public static String getSafeVariableName(String name, String... existingVariableNames) { return getSafeVariableName( name, Arrays.asList( existingVariableNames ) ); } @@ -153,12 +159,12 @@ public static String getSafeVariableName(String name, Collection existin } int c = 1; - String seperator = Character.isDigit( name.charAt( name.length() - 1 ) ) ? "_" : ""; - while ( conflictingNames.contains( name + seperator + c ) ) { + String separator = Character.isDigit( name.charAt( name.length() - 1 ) ) ? "_" : ""; + while ( conflictingNames.contains( name + separator + c ) ) { c++; } - return name + seperator + c; + return name + separator + c; } /** @@ -166,7 +172,35 @@ public static String getSafeVariableName(String name, Collection existin * @return the identifier without any characters that are not allowed as part of a Java identifier. */ public static String sanitizeIdentifierName(String identifier) { - return identifier.replace( "[]", "Array" ); + 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; } /** 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/ValueProvider.java b/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java deleted file mode 100644 index 28c706bef0..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/ValueProvider.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 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 org.mapstruct.ap.internal.util.accessor.Accessor; - -/** - * This a wrapper class which provides the value that needs to be used in the models. - * - * It is used to provide the read value for a difference kind of {@link Accessor}. - * - * @author Filip Hrisafov - */ -public class ValueProvider { - - private final String value; - - private ValueProvider(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - - @Override - public String toString() { - return value; - } - - /** - * Creates a {@link ValueProvider} from the provided {@code accessor}. The base value is - * {@link Accessor#getSimpleName()}. If the {@code accessor} is for an executable, then {@code ()} is - * appended. - * - * @param accessor that provides the value - * - * @return a {@link ValueProvider} tha provides a read value for the {@code accessor} - */ - public static ValueProvider of(Accessor accessor) { - if ( accessor == null ) { - return null; - } - String value = accessor.getSimpleName().toString(); - if ( accessor.getExecutable() != null ) { - value += "()"; - } - return new ValueProvider( value ); - } -} 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 index 8216e83121..532b9f0bce 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/XmlConstants.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/XmlConstants.java @@ -12,19 +12,14 @@ */ public final class XmlConstants { - public static final String JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR = "javax.xml.datatype.XMLGregorianCalendar"; - private static final boolean IS_XML_GREGORIAN_CALENDAR_PRESENT = ClassUtils.isPresent( - JAVAX_XML_DATATYPE_XMLGREGORIAN_CALENDAR, - XmlConstants.class.getClassLoader() - ); + // 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() { } - /** - * @return {@code true} if the {@link javax.xml.datatype.XMLGregorianCalendar} is present, {@code false} otherwise - */ - public static boolean isXmlGregorianCalendarPresent() { - return IS_XML_GREGORIAN_CALENDAR_PRESENT; - } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java deleted file mode 100644 index b5b671c2dd..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/AbstractAccessor.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 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.Name; - -/** - * This is an abstract implementation of an {@link Accessor} that provides the common implementation. - * - * @author Filip Hrisafov - */ -abstract class AbstractAccessor implements Accessor { - - protected final T element; - - AbstractAccessor(T element) { - this.element = element; - } - - @Override - public Name getSimpleName() { - return element.getSimpleName(); - } - - @Override - public Set getModifiers() { - return element.getModifiers(); - } - - @Override - public T getElement() { - return element; - } -} 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 index 9fb0969807..d624b7788a 100644 --- 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 @@ -9,7 +9,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; -import javax.lang.model.element.Name; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** @@ -23,7 +23,7 @@ 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 javax.lang.model.element.VariableElement#asType()} for field accessors. + * or {@link VariableElement#asType()} for field accessors. * * @return the type that the accessor gives as a return */ @@ -32,7 +32,7 @@ public interface Accessor { /** * @return the simple name of the accessor */ - Name getSimpleName(); + String getSimpleName(); /** * @return the set of modifiers that the accessor has @@ -40,12 +40,12 @@ public interface Accessor { Set getModifiers(); /** - * @return the {@link ExecutableElement}, or {@code null} if the accessor does not have one + * @return the underlying {@link Element}, {@link VariableElement} or {@link ExecutableElement} */ - ExecutableElement getExecutable(); + Element getElement(); /** - * @return the underlying {@link Element} + * @return type of the accessor */ - Element getElement(); + 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/ExecutableElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java deleted file mode 100644 index effd137b04..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/ExecutableElementAccessor.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 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; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps an {@link ExecutableElement}. - * - * @author Filip Hrisafov - */ -public class ExecutableElementAccessor extends AbstractAccessor { - - public ExecutableElementAccessor(ExecutableElement element) { - super( element ); - } - - @Override - public TypeMirror getAccessedType() { - return element.getReturnType(); - } - - @Override - public ExecutableElement getExecutable() { - return element; - } - - @Override - public String toString() { - return element.toString(); - } -} 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/accessor/VariableElementAccessor.java b/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/VariableElementAccessor.java deleted file mode 100644 index cdbdf1f09f..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/accessor/VariableElementAccessor.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 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; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeMirror; - -/** - * An {@link Accessor} that wraps a {@link VariableElement}. - * - * @author Filip Hrisafov - */ -public class VariableElementAccessor extends AbstractAccessor { - - public VariableElementAccessor(VariableElement element) { - super( element ); - } - - @Override - public TypeMirror getAccessedType() { - return element.asType(); - } - - @Override - public ExecutableElement getExecutable() { - return null; - } - - @Override - public String toString() { - return element.toString(); - } -} 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/workarounds/EclipseAsMemberOfWorkaround.java b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseAsMemberOfWorkaround.java deleted file mode 100644 index 54aebe03be..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseAsMemberOfWorkaround.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 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.workarounds; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.Types; - -import org.eclipse.jdt.core.compiler.CharOperation; -import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl; -import org.eclipse.jdt.internal.compiler.apt.model.ElementImpl; -import org.eclipse.jdt.internal.compiler.lookup.MethodBinding; -import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; -import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; - -/** - * Contains the workaround for {@link Types#asMemberOf(DeclaredType, Element)} using Eclipse implementation types. - *

      - * This class may only be accessed through {@link EclipseClassLoaderBridge} when running within Eclipse - * - * @author Andreas Gudian - */ -final class EclipseAsMemberOfWorkaround { - private EclipseAsMemberOfWorkaround() { - } - - /** - * Eclipse-specific implementation of {@link Types#asMemberOf(DeclaredType, Element)}. - *

      - * Returns {@code null} if the implementation could not determine the result. - */ - static TypeMirror asMemberOf(ProcessingEnvironment environment, DeclaredType containing, - Element element) { - - ElementImpl elementImpl = tryCast( element, ElementImpl.class ); - BaseProcessingEnvImpl env = tryCast( environment, BaseProcessingEnvImpl.class ); - - if ( elementImpl == null || env == null ) { - return null; - } - - ReferenceBinding referenceBinding = - (ReferenceBinding) ( (ElementImpl) environment.getTypeUtils().asElement( containing ) )._binding; - - MethodBinding methodBinding = (MethodBinding) elementImpl._binding; - - // matches in super-classes have priority - MethodBinding inSuperclassHiearchy = findInSuperclassHierarchy( methodBinding, referenceBinding ); - - if ( inSuperclassHiearchy != null ) { - return env.getFactory().newTypeMirror( inSuperclassHiearchy ); - } - - // if nothing was found, traverse the interfaces and collect all candidate methods that match - List candidatesFromInterfaces = new ArrayList<>(); - - collectFromInterfaces( - methodBinding, - referenceBinding, - new HashSet<>(), - candidatesFromInterfaces ); - - // there can be multiple matches for the same method name from adjacent interface hierarchies. - Collections.sort( candidatesFromInterfaces, MostSpecificMethodBindingComparator.INSTANCE ); - - if ( !candidatesFromInterfaces.isEmpty() ) { - // return the most specific match - return env.getFactory().newTypeMirror( candidatesFromInterfaces.get( 0 ) ); - } - - return null; - } - - private static T tryCast(Object instance, Class type) { - if ( instance != null && type.isInstance( instance ) ) { - return type.cast( instance ); - } - - return null; - } - - private static void collectFromInterfaces(MethodBinding methodBinding, ReferenceBinding typeBinding, - Set visitedTypes, List found) { - if ( typeBinding == null ) { - return; - } - - // also check the interfaces of the superclass hierarchy (the superclasses themselves don't contain a match, - // we checked that already) - collectFromInterfaces( methodBinding, typeBinding.superclass(), visitedTypes, found ); - - for ( ReferenceBinding ifc : typeBinding.superInterfaces() ) { - if ( visitedTypes.contains( ifc ) ) { - continue; - } - - visitedTypes.add( ifc ); - - // finding a match in one interface - MethodBinding f = findMatchingMethodBinding( methodBinding, ifc.methods() ); - - if ( f == null ) { - collectFromInterfaces( methodBinding, ifc, visitedTypes, found ); - } - else { - // no need for recursion if we found a candidate in this type already - found.add( f ); - } - } - } - - /** - * @param baseMethod binding to compare against - * @param methods the candidate methods - * @return The method from the list of candidates that matches the name and original/erasure of - * {@code methodBinding}, or {@code null} if none was found. - */ - private static MethodBinding findMatchingMethodBinding(MethodBinding baseMethod, MethodBinding[] methods) { - for ( MethodBinding method : methods ) { - if ( CharOperation.equals( method.selector, baseMethod.selector ) - && ( method.original() == baseMethod || method.areParameterErasuresEqual( baseMethod ) ) ) { - return method; - } - } - - return null; - } - - private static MethodBinding findInSuperclassHierarchy(MethodBinding baseMethod, ReferenceBinding typeBinding) { - while ( typeBinding != null ) { - MethodBinding matching = findMatchingMethodBinding( baseMethod, typeBinding.methods() ); - if ( matching != null ) { - return matching; - } - - typeBinding = typeBinding.superclass(); - } - - return null; - } - - /** - * Compares MethodBindings by their signature: the more specific method is considered lower. - * - * @author Andreas Gudian - */ - private static final class MostSpecificMethodBindingComparator implements Comparator { - private static final MostSpecificMethodBindingComparator INSTANCE = new MostSpecificMethodBindingComparator(); - - @Override - public int compare(MethodBinding first, MethodBinding second) { - boolean firstParamsAssignableFromSecond = - first.areParametersCompatibleWith( second.parameters ); - boolean secondParamsAssignableFromFirst = - second.areParametersCompatibleWith( first.parameters ); - - if ( firstParamsAssignableFromSecond != secondParamsAssignableFromFirst ) { - return firstParamsAssignableFromSecond ? 1 : -1; - } - - if ( TypeBinding.equalsEquals( first.returnType, second.returnType ) ) { - return 0; - } - - boolean firstReturnTypeAssignableFromSecond = - second.returnType.isCompatibleWith( first.returnType ); - - return firstReturnTypeAssignableFromSecond ? 1 : -1; - } - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseClassLoaderBridge.java b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseClassLoaderBridge.java deleted file mode 100644 index d7dee52fb4..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/EclipseClassLoaderBridge.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 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.workarounds; - -import java.lang.reflect.Method; -import java.net.URLClassLoader; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; - -/** - * In Eclipse 4.6, the ClassLoader of the annotation processor does not provide access to the implementation types of - * the APT-API anymore, so we need to create a new ClassLoader containing the processor class path URLs and having the - * ClassLoader of the APT-API implementations as parent. The method invocation then consequently needs to be done via - * reflection. - * - * @author Andreas Gudian - */ -class EclipseClassLoaderBridge { - private static final String ECLIPSE_AS_MEMBER_OF_WORKAROUND = - "org.mapstruct.ap.internal.util.workarounds.EclipseAsMemberOfWorkaround"; - - private static ClassLoader classLoader; - private static Method asMemberOf; - - private EclipseClassLoaderBridge() { - } - - /** - * Invokes {@link EclipseAsMemberOfWorkaround#asMemberOf(ProcessingEnvironment, DeclaredType, Element)} via - * reflection using a special ClassLoader. - */ - static TypeMirror invokeAsMemberOfWorkaround(ProcessingEnvironment env, DeclaredType containing, Element element) - throws Exception { - return (TypeMirror) getAsMemberOf( element.getClass().getClassLoader() ).invoke( - null, - env, - containing, - element ); - } - - private static ClassLoader getOrCreateClassLoader(ClassLoader parent) { - if ( classLoader == null ) { - classLoader = new URLClassLoader( - ( (URLClassLoader) EclipseClassLoaderBridge.class.getClassLoader() ).getURLs(), - parent ); - } - - return classLoader; - } - - private static Method getAsMemberOf(ClassLoader platformClassLoader) throws Exception { - if ( asMemberOf == null ) { - Class workaroundClass = - getOrCreateClassLoader( platformClassLoader ).loadClass( ECLIPSE_AS_MEMBER_OF_WORKAROUND ); - - Method found = workaroundClass.getDeclaredMethod( - "asMemberOf", - ProcessingEnvironment.class, - DeclaredType.class, - Element.class ); - - found.setAccessible( true ); - - asMemberOf = found; - } - - return asMemberOf; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/SpecificCompilerWorkarounds.java b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/SpecificCompilerWorkarounds.java deleted file mode 100644 index 69b45c1ca7..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/SpecificCompilerWorkarounds.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 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.workarounds; - -import javax.annotation.processing.ProcessingEnvironment; -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 javax.lang.model.util.Elements; -import javax.lang.model.util.Types; - -import org.mapstruct.ap.internal.version.VersionInformation; - -/** - * 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, checking for VOID first. - * - * @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 - */ - static boolean isAssignable(Types types, TypeMirror t1, TypeMirror t2) { - if ( t1.getKind() == TypeKind.VOID ) { - return false; - } - - return types.isAssignable( t1, 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 - */ - 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 - */ - 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; - } - - /** - * Workaround for Bugs in the Eclipse implementation of {@link Types#asMemberOf(DeclaredType, Element)}. - * - * @see Eclipse Bug 382590 (fixed in Eclipse 4.6) - * @see Eclipse Bug 481555 - */ - static TypeMirror asMemberOf(Types typeUtils, ProcessingEnvironment env, VersionInformation versionInformation, - DeclaredType containing, Element element) { - TypeMirror result = null; - Exception lastException = null; - try { - try { - result = typeUtils.asMemberOf( containing, element ); - } - catch ( IllegalArgumentException e ) { - lastException = e; - if ( versionInformation.isEclipseJDTCompiler() ) { - result = EclipseClassLoaderBridge.invokeAsMemberOfWorkaround( env, containing, element ); - } - } - } - catch ( Exception e ) { - lastException = e; - } - - if ( null == result ) { - throw new RuntimeException( "Fallback implementation of asMemberOf didn't work for " - + element + " in " + containing, lastException ); - } - - return result; - } -} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/TypesDecorator.java b/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/TypesDecorator.java deleted file mode 100644 index 097ab52631..0000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/workarounds/TypesDecorator.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 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.workarounds; - -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; - -import org.mapstruct.ap.internal.version.VersionInformation; - -/** - * Replaces the usage of {@link Types} within MapStruct by delegating to the original implementation or to our specific - * workarounds if necessary. - * - * @author Andreas Gudian - */ -public class TypesDecorator implements Types { - private final Types delegate; - private final ProcessingEnvironment processingEnv; - private final VersionInformation versionInformation; - - public TypesDecorator(ProcessingEnvironment processingEnv, VersionInformation versionInformation) { - this.delegate = processingEnv.getTypeUtils(); - this.processingEnv = processingEnv; - this.versionInformation = versionInformation; - } - - @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 SpecificCompilerWorkarounds.isSubtype( delegate, t1, t2 ); - } - - @Override - public boolean isAssignable(TypeMirror t1, TypeMirror t2) { - return SpecificCompilerWorkarounds.isAssignable( delegate, 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 SpecificCompilerWorkarounds.erasure( delegate, 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 SpecificCompilerWorkarounds.asMemberOf( - delegate, - processingEnv, - versionInformation, - containing, - element ); - } -} 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 94e3520ad0..411e11bf9e 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 @@ -21,6 +21,10 @@ public interface VersionInformation { boolean isSourceVersionAtLeast9(); + boolean isSourceVersionAtLeast11(); + + boolean isSourceVersionAtLeast19(); + boolean isEclipseJDTCompiler(); boolean isJavacCompiler(); 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 cb2e2a911f..2c3f762c0d 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 @@ -12,7 +12,7 @@ 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; @@ -62,16 +62,13 @@ public class ModelWriter { } public void writeModel(FileObject sourceFile, Writable model) { - try { - BufferedWriter writer = new BufferedWriter( new IndentationCorrectingWriter( sourceFile.openWriter() ) ); - - Map, Object> values = new HashMap<>(); - values.put( Configuration.class, CONFIGURATION ); + try ( BufferedWriter writer = new BufferedWriter( new IndentationCorrectingWriter( sourceFile.openWriter() ))) { + Map, Object> values = new HashMap<>(); + 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; @@ -100,7 +97,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 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 c9d0734753..b4dde64324 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/AccessorNamingStrategy.java @@ -68,7 +68,7 @@ default void init(MapStructProcessingEnvironment processingEnvironment) { * * @return getter name for collection properties * - * @deprecated MapStuct will not call this method anymore. Use {@link #getMethodType(ExecutableElement)} to + * @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. */ 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 index 8ac41395bf..6b9e079523 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/AstModifyingAnnotationProcessor.java @@ -15,7 +15,7 @@ *

      * 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 annnotation processing + * 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 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java b/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java index 0f95567481..5e37987f75 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/BuilderInfo.java @@ -54,6 +54,10 @@ public static class Builder { private Collection buildMethods; /** + * @param method The creation method for the builder + * + * @return the builder for chaining + * * @see BuilderInfo#getBuilderCreationMethod() */ public Builder builderCreationMethod(ExecutableElement method) { @@ -62,6 +66,10 @@ public Builder builderCreationMethod(ExecutableElement method) { } /** + * @param methods the build methods for the type + * + * @return the builder for chaining + * * @see BuilderInfo#getBuildMethods() */ public Builder buildMethod(Collection methods) { @@ -71,6 +79,9 @@ public Builder buildMethod(Collection methods) { /** * Create the {@link BuilderInfo}. + * + * @return the created {@link BuilderInfo} + * * @throws IllegalArgumentException if the builder creation or build methods are {@code null} */ public BuilderInfo build() { 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 index 6a8c9baafb..a726e9691f 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultAccessorNamingStrategy.java @@ -7,16 +7,20 @@ 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.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +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; /** @@ -28,6 +32,24 @@ 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; @@ -57,7 +79,8 @@ else if ( isPresenceCheckMethod( method ) ) { } /** - * Returns {@code true} when the {@link ExecutableElement} is a getter method. A method is a getter when it starts + * 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. @@ -69,6 +92,10 @@ else if ( isPresenceCheckMethod( method ) ) { * @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 && @@ -81,7 +108,6 @@ public boolean isGetterMethod(ExecutableElement method) { 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. @@ -98,10 +124,48 @@ public boolean isSetterMethod(ExecutableElement method) { } protected boolean isFluentSetter(ExecutableElement method) { - return method.getParameters().size() == 1 && - !JAVA_JAVAX_PACKAGE.matcher( method.getEnclosingElement().asType().toString() ).matches() && - !isAdderWithUpperCase4thCharacter( method ) && - typeUtils.isAssignable( method.getReturnType(), method.getEnclosingElement().asType() ); + 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; } /** @@ -124,7 +188,6 @@ private boolean isAdderWithUpperCase4thCharacter(ExecutableElement method) { * {@link #getElementName(ExecutableElement) }. *

      * The calling MapStruct code guarantees there's only one argument. - *

      * * @param method to be analyzed * @@ -166,11 +229,22 @@ public boolean isPresenceCheckMethod(ExecutableElement method) { @Override public String getPropertyName(ExecutableElement getterOrSetterMethod) { String methodName = getterOrSetterMethod.getSimpleName().toString(); - if ( methodName.startsWith( "get" ) || methodName.startsWith( "set" ) ) { - return IntrospectorUtils.decapitalize( methodName.substring( 3 ) ); - } - else if ( isFluentSetter( getterOrSetterMethod ) ) { - return methodName; + 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 ) ); } @@ -199,7 +273,7 @@ public String getElementName(ExecutableElement adderMethod) { */ protected static String getQualifiedName(TypeMirror type) { DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { + new SimpleTypeVisitor8() { @Override public DeclaredType visitDeclared(DeclaredType t, Void p) { return t; @@ -213,7 +287,7 @@ public DeclaredType visitDeclared(DeclaredType t, Void p) { } TypeElement typeElement = declaredType.asElement().accept( - new SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; diff --git a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java index f5656e2e48..c1126fa9fb 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/DefaultBuilderProvider.java @@ -10,16 +10,19 @@ 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.SimpleElementVisitor6; -import javax.lang.model.util.SimpleTypeVisitor6; +import javax.lang.model.util.SimpleElementVisitor8; +import javax.lang.model.util.SimpleTypeVisitor8; import javax.lang.model.util.Types; /** @@ -107,25 +110,23 @@ public BuilderInfo findBuilderInfo(TypeMirror type) { * @throws TypeHierarchyErroneousException if the {@link TypeMirror} is of kind {@link TypeKind#ERROR} */ protected TypeElement getTypeElement(TypeMirror type) { - if ( type.getKind() == TypeKind.ERROR ) { - throw new TypeHierarchyErroneousException( type ); - } - DeclaredType declaredType = type.accept( - new SimpleTypeVisitor6() { - @Override - public DeclaredType visitDeclared(DeclaredType t, Void p) { - return t; - } - }, - null - ); + 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 SimpleElementVisitor6() { + new SimpleElementVisitor8() { @Override public TypeElement visitType(TypeElement e, Void p) { return e; @@ -135,6 +136,28 @@ public TypeElement visitType(TypeElement e, Void p) { ); } + /** + * 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}. *

      @@ -155,34 +178,105 @@ public TypeElement visitType(TypeElement e, Void p) { * @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; } - List methods = ElementFilter.methodsIn( typeElement.getEnclosedElements() ); - List builderInfo = new ArrayList<>(); - for ( ExecutableElement method : methods ) { - if ( isPossibleBuilderCreationMethod( method, typeElement ) ) { - TypeElement builderElement = getTypeElement( method.getReturnType() ); - Collection buildMethods = findBuildMethods( builderElement, typeElement ); - if ( !buildMethods.isEmpty() ) { - builderInfo.add( new BuilderInfo.Builder() - .builderCreationMethod( method ) - .buildMethod( buildMethods ) - .build() - ); + // 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() ); } - if ( builderInfo.size() == 1 ) { - return builderInfo.get( 0 ); + 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(); + } } - else if ( builderInfo.size() > 1 ) { - throw new MoreThanOneBuilderCreationMethodException( typeElement.asType(), builderInfo ); + + 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 findBuilderInfo( typeElement.getSuperclass() ); + return null; } /** @@ -204,28 +298,46 @@ protected boolean isPossibleBuilderCreationMethod(ExecutableElement method, Type return method.getParameters().isEmpty() && method.getModifiers().contains( Modifier.PUBLIC ) && method.getModifiers().contains( Modifier.STATIC ) - && !typeUtils.isSameType( method.getReturnType(), typeElement.asType() ); + && 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, TypeElement)} to check if the method is a - * build method for {@code typeElement}. + * {@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. - *

      - * If there are multiple methods that satisfy - * {@link DefaultBuilderProvider#isBuildMethod(ExecutableElement, TypeElement)} and one of those methods - * is names {@code build} that that method would be considered as a build method. + * * @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(); } @@ -233,23 +345,57 @@ protected Collection findBuildMethods(TypeElement builderElem List builderMethods = ElementFilter.methodsIn( builderElement.getEnclosedElements() ); List buildMethods = new ArrayList<>(); for ( ExecutableElement buildMethod : builderMethods ) { - if ( isBuildMethod( buildMethod, typeElement ) ) { + if ( isBuildMethod( buildMethod, builderType, typeElement ) ) { buildMethods.add( buildMethod ); } } - if ( buildMethods.isEmpty() ) { - return findBuildMethods( - getTypeElement( builderElement.getSuperclass() ), + 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 buildMethods; + 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 {@code typeElement}. + * 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: *

        @@ -259,14 +405,23 @@ protected Collection findBuildMethods(TypeElement builderElem *
      * * @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, TypeElement typeElement) { - return buildMethod.getParameters().isEmpty() && - buildMethod.getModifiers().contains( Modifier.PUBLIC ) - && typeUtils.isAssignable( buildMethod.getReturnType(), typeElement.asType() ); + 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; } /** 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 index 92fef3a1be..dc14e20de6 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/FreeBuilderAccessorNamingStrategy.java @@ -20,8 +20,8 @@ *
    15. {@code mergeFrom(Target.Builder)}
    16. * *

      - * 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 + * 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 diff --git a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java index 69c66cdd28..1df6ebc1b6 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesAccessorNamingStrategy.java @@ -10,9 +10,9 @@ import org.mapstruct.util.Experimental; /** - * Accesor naming strategy for Immutables. + * 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. + * as a setter with a name {@code from}. Therefore, we are ignoring it. * * @author Filip Hrisafov */ @@ -21,7 +21,18 @@ public class ImmutablesAccessorNamingStrategy extends DefaultAccessorNamingStrat @Override protected boolean isFluentSetter(ExecutableElement method) { - return super.isFluentSetter( method ) && !method.getSimpleName().toString().equals( "from" ); + 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 index d4ccd029ef..3431808418 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/ImmutablesBuilderProvider.java @@ -35,18 +35,31 @@ protected BuilderInfo findBuilderInfo(TypeElement typeElement) { 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 ) { - BuilderInfo info = findBuilderInfoForImmutables( + return findBuilderInfoForImmutables( typeElement, immutableAnnotation ); - if ( info != null ) { - return info; - } } - - return super.findBuilderInfo( typeElement ); + return null; } protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement, @@ -55,7 +68,7 @@ protected BuilderInfo findBuilderInfoForImmutables(TypeElement typeElement, if ( typeUtils.isSameType( annotationMirror.getAnnotationType(), immutableAnnotation.asType() ) ) { TypeElement immutableElement = asImmutableElement( typeElement ); if ( immutableElement != null ) { - return super.findBuilderInfo( immutableElement ); + return super.findBuilderInfo( immutableElement, false ); } else { // Immutables processor has not run yet. Trigger a postpone to the next round for MapStruct diff --git a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java index 0471108a51..0cea4369eb 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MapStructProcessingEnvironment.java @@ -7,6 +7,7 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; +import java.util.Map; /** * MapStruct will provide the implementations of its SPIs with on object implementing this interface so they can use @@ -36,4 +37,12 @@ public interface MapStructProcessingEnvironment { */ 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 index afd7e69511..2a1cacfb1f 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/MappingExclusionProvider.java @@ -12,10 +12,9 @@ /** * 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 @@ -30,14 +29,14 @@ *
      • 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 it's signature changed in subsequent releases") +@Experimental("This SPI can have its signature changed in subsequent releases") public interface MappingExclusionProvider { /** @@ -46,7 +45,6 @@ public interface MappingExclusionProvider { * 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/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/util/IntrospectorUtils.java b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java index fccc22b38b..95dcd7a92d 100644 --- a/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java +++ b/processor/src/main/java/org/mapstruct/ap/spi/util/IntrospectorUtils.java @@ -32,7 +32,7 @@ private IntrospectorUtils() { * @return The decapitalized version of the string. */ public static String decapitalize(String name) { - if ( name == null || name.length() == 0 ) { + if ( name == null || name.isEmpty() ) { return name; } if ( name.length() > 1 && Character.isUpperCase( name.charAt( 1 ) ) && 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 index 7b8f374cf7..93bccf51d0 100644 --- 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 @@ -4,4 +4,7 @@ * 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/org.mapstruct.ap.internal.processor.ModelElementProcessor b/processor/src/main/resources/META-INF/services/org.mapstruct.ap.internal.processor.ModelElementProcessor index 7b27e54faf..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 @@ -3,7 +3,9 @@ # 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 index ce0f605f11..6753a73d6f 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/conversion/CreateDecimalFormat.ftl @@ -5,9 +5,10 @@ Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 --> -private DecimalFormat ${name}( String numberFormat ) { +<#-- @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 ); + DecimalFormat df = new DecimalFormat( numberFormat<#if parameters.size() > 1>, DecimalFormatSymbols.getInstance( locale ) ); df.setParseBigDecimal( true ); return df; -} \ No newline at end of file +} 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/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 4391bcb975..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 @@ -6,4 +6,15 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.Annotation" --> -@<@includeModel object=type/><#if (properties?size > 0) >(<#list properties as property>${property}<#if property_has_next>, ) \ No newline at end of file +<#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/BeanMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/BeanMappingMethod.ftl index 2d702efe2d..4ad6ae20db 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 @@ -6,11 +6,13 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.BeanMappingMethod" --> -<#if overridden>@Override +<#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 = resultType.effectiveType /> + <#assign targetType = returnTypeToConstruct /> <#list beforeMappingReferencesWithoutMappingTarget as callback> <@includeModel object=callback targetBeanName=resultName targetType=targetType/> @@ -18,14 +20,94 @@ - <#if !mapNullToDefault> - if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { - return<#if returnType.name != "void"> null; + <#list beforeMappingReferencesWithFinalizedReturnType as callback> + <@includeModel object=callback targetBeanName=finalizedResultName targetType=finalizedReturnType/> + <#if !callback_has_next> + + + + <#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.effectiveType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType=resultType.effectiveType/><#else>new <@includeModel object=resultType.effectiveType/>(); + <#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>new <@includeModel object=returnTypeToConstruct/>(); + <#list beforeMappingReferencesWithMappingTarget as callback> @@ -35,24 +117,34 @@ <#if (sourceParameters?size > 1)> - <#list sourceParametersExcludingPrimitives as sourceParam> + <#list sourceParametersNeedingPresenceCheck as sourceParam> <#if (propertyMappingsByParameter(sourceParam)?size > 0)> - if ( ${sourceParam.name} != null ) { + 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> + <#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 ) { + <#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/> @@ -69,11 +161,28 @@ <#if returnType.name != "void"> - <#if finalizerMethod??> - return ${resultName}.<@includeModel object=finalizerMethod />; - <#else> - 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> @@ -83,4 +192,27 @@ <#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/DefaultMapperReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/DefaultMapperReference.ftl index e4eeddb4b5..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 @@ -6,4 +6,4 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.DefaultMapperReference" --> -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 +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/EnumMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/EnumMappingMethod.ftl deleted file mode 100644 index 3d0c9e074b..0000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/EnumMappingMethod.ftl +++ /dev/null @@ -1,44 +0,0 @@ -<#-- - - 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.EnumMappingMethod" --> -@Override -public <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { - <#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}; -} 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 3bdae0582a..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 @@ -14,6 +14,7 @@ package ${packageName}; import ${importedType}; +<#if javadoc??><#nt><@includeModel object=javadoc/> <#if !generatedTypeAvailable>/* @Generated( value = "org.mapstruct.ap.MappingProcessor"<#if suppressGeneratorTimestamp == false>, @@ -24,7 +25,7 @@ import ${importedType}; <#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 index d49397a985..d083bd113d 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/IterableCreation.ftl @@ -11,13 +11,15 @@ <@includeModel object=factoryMethod targetType=resultType/> <#elseif enumSet> EnumSet.noneOf( <@includeModel object=enumSetElementType raw=true/>.class ) - <#else> - new - <#if resultType.implementationType??> - <@includeModel object=resultType.implementationType/><#if ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize>( <@sizeForCreation /> )<#else>() + <#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=resultType/>() + new <@includeModel object=resultType.implementationType/><#if ext.useSizeIfPossible?? && ext.useSizeIfPossible && canUseSize>( <@sizeForCreation /> )<#else>() + <#else> + new <@includeModel object=resultType/>() + <#macro sizeForCreation> <@compress single_line=true> 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 3af75079b9..7c3d3071ac 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 @@ -6,7 +6,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.IterableMappingMethod" --> -<#if overridden>@Override +<#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/> @@ -14,10 +16,9 @@ - if ( ${sourceParameter.name} == null ) { + 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> 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/MapMappingMethod.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MapMappingMethod.ftl index 1c6e6bad35..957dc712ee 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 @@ -6,7 +6,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MapMappingMethod" --> -<#if overridden>@Override +<#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/> @@ -14,9 +16,9 @@ - if ( ${sourceParameter.name} == null ) { + 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(); 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 30a830bc57..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 @@ -15,7 +15,11 @@ <#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/>.<@methodCall/> + <@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> <@methodCall/> @@ -37,9 +41,13 @@ <#-- a class is passed on for casting, see @TargetType --> <@includeModel object=inferTypeWhenEnum( ext.targetType ) raw=true/>.class<#t> <#elseif param.mappingTarget> - ${ext.targetBeanName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> + <#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??> @@ -54,13 +62,16 @@ <#-- macro: assignment - purpose: note: takes its targetyType from the singleSourceParameterType + 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/> - \ No newline at end of file + 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 e50e55bc34..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 @@ -7,29 +7,19 @@ --> <#-- @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/> { - if ( ${sourceParameter.name} == null ) { - return ${returnType.null}; - } <#list propertyEntries as entry> - <#if entry.presenceCheckerName?? > - if ( !<@localVarName index=entry_index/>.${entry.presenceCheckerName}() ) { - return ${returnType.null}; - } - - <@includeModel object=entry.type.typeBound/> ${entry.name} = <@localVarName index=entry_index/>.${entry.accessorName}; - <#if !entry.presenceCheckerName?? > - <#if !entry.type.primitive> - if ( ${entry.name} == null ) { + <#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 localVarName index><#if index == 0>${sourceParameter.name}<#else>${propertyEntries[index-1].name} <#macro throws> <#if (thrownTypes?size > 0)><#lt> throws <@compress single_line=true> <#list thrownTypes as exceptionType> 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 20e6f1734c..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 @@ -11,5 +11,7 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=targetReadAccessorName targetWriteAccessorName=targetWriteAccessorName + sourcePropertyName=sourcePropertyName + targetPropertyName=name targetType=targetType defaultValueAssignment=defaultValueAssignment /> \ No newline at end of file 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 index f9a7309b5d..2269892b8a 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/StreamMappingMethod.ftl @@ -6,7 +6,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.StreamMappingMethod" --> -<#if overridden>@Override +<#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> @@ -15,10 +17,9 @@ - if ( ${sourceParameter.name} == null ) { + 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> @@ -55,7 +56,7 @@ <#if needVarDefine> <#assign needVarDefine = false /> <#-- We create a null array which later will be directly assigned from the stream--> - ${resultElementType}[] ${resultName} = null; + <@includeModel object=resultElementType/>[] ${resultName} = null; <#elseif resultType.iterableType> <#if existingInstanceMapping> @@ -112,7 +113,7 @@ <#else> <#-- Streams are immutable so we can't update them --> <#if !existingInstanceMapping> - <#--TODO fhr: after the the result is no longer the same instance, how does it affect the + <#--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 />; 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 370fe7c978..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 @@ -14,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 index 5fa1c0a118..104fd98437 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/ValueMappingMethod.ftl @@ -6,8 +6,11 @@ --> <#-- @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>, ) { +<#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> @@ -15,17 +18,17 @@ if ( ${sourceParameter.name} == null ) { - return <#if nullTarget??><@includeModel object=returnType/>.${nullTarget}<#else>null; + <#if nullTarget.targetAsException>throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>return <@writeTarget target=nullTarget.target/>; } <@includeModel object=resultType/> ${resultName}; switch ( ${sourceParameter.name} ) { <#list valueMappings as valueMapping> - case ${valueMapping.source}: ${resultName} = <#if valueMapping.target??><@includeModel object=returnType/>.${valueMapping.target}<#else>null; - break; + 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 throwIllegalArgumentException>throw new IllegalArgumentException( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <#if defaultTarget??><@includeModel object=returnType/>.${defaultTarget}<#else>null; + 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> @@ -40,5 +43,33 @@ <@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 361fedaa58..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 @@ -11,7 +11,7 @@ <@lib.sourceLocalVarAssignment/> <@lib.handleSourceReferenceNullCheck> for ( <@includeModel object=adderType.typeBound/> ${sourceLoopVarName} : <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} ) { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>; + <#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 edb0d3cad3..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 @@ -10,6 +10,6 @@ <@lib.handleExceptions> <@lib.sourceLocalVarAssignment/> <@lib.handleSourceReferenceNullCheck> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite>Arrays.copyOf( ${sourceLocalVarName}, ${sourceLocalVarName}.length ); + <#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 index 7364f7922e..449a273614 100644 --- 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 @@ -6,4 +6,4 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.assignment.EnumConstantWrapper" --> -${ext.targetType.name}.${assignment} \ No newline at end of file +${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 index cee722e2d3..8e771f0ddb 100644 --- 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 @@ -15,8 +15,12 @@ ${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 --> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; - } + <#if mapNullToClear> + ${ext.targetBeanName}.${ext.targetReadAccessorName}.clear(); + <#else > + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if mapNullToDefault><@lib.initTargetObject/><#else>null; + + } } else { @@ -30,6 +34,10 @@ <@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) 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 4ef55f3b40..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 @@ -10,10 +10,13 @@ <@lib.sourceLocalVarAssignment/> if ( ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing /> != null ) { <@lib.handleExceptions> - <#if ext.existingInstanceMapping> + <#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/> ); 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 index 2795dac7bc..4c3b6d4804 100644 --- 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 @@ -6,6 +6,7 @@ --> <#-- @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> @@ -17,14 +18,9 @@ <#else> <#compress> ${sourceVarName} -> { - try { + <@lib.handleExceptions> return <@_assignment/>; - } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { - throw new RuntimeException( e ); - } - + } @@ -34,5 +30,6 @@ 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 53e987e476..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 @@ -6,18 +6,14 @@ --> <#-- @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 @@ -25,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/NewInstanceSetterWrapperForCollectionsAndMaps.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/NewInstanceSetterWrapperForCollectionsAndMaps.ftl new file mode 100644 index 0000000000..4582e68a1e --- /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} = new <#if ext.targetType.implementationType??><@includeModel object=ext.targetType.implementationType/><#else><@includeModel object=ext.targetType/>(); + ${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/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 613e8f6abd..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 @@ -10,6 +10,6 @@ <@lib.handleExceptions> <@lib.sourceLocalVarAssignment/> <@lib.handleSourceReferenceNullCheck> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>; + <#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 122c76bc62..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 @@ -9,5 +9,5 @@ <#import "../macro/CommonMacros.ftl" as lib> <@lib.sourceLocalVarAssignment/> <@lib.handleExceptions> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.handleAssignment/>; + <#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 index c26557af94..3d6172b49d 100644 --- 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 @@ -16,7 +16,7 @@ --> <#macro callTargetWriteAccessor> <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>; + <#if ext.targetBeanName?has_content>${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/>; <#-- 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 58416fc63b..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 @@ -6,17 +6,19 @@ --> <#-- @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 ( <#if sourcePresenceCheckerReference?? >${sourcePresenceCheckerReference}<#else><#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} != null ) { + if ( <@handleSourceReferenceNullCheck/> ) { <@assignToExistingTarget/> <@lib.handleAssignment/>; } <#if setExplicitlyToDefault || setExplicitlyToNull> else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null; + ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull>(<@includeModel object=ext.targetType/>) ${ext.targetType.null}; } <#else> @@ -32,3 +34,16 @@ ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/>; } + +<#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/source/builtin/FinalField.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/FinalField.ftl similarity index 100% rename from processor/src/main/resources/org/mapstruct/ap/internal/model/source/builtin/FinalField.ftl rename to processor/src/main/resources/org/mapstruct/ap/internal/model/common/FinalField.ftl 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/SourceRHS.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/common/SourceRHS.ftl index 0b60bd39ae..aaf1eb8df4 100644 --- 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 @@ -6,4 +6,4 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.SourceRHS" --> -<#if sourceLoopVarName??>${sourceLoopVarName}<#elseif sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference} \ No newline at end of file +<#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 89b2279a6d..ee7d0b106f 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 @@ -7,9 +7,9 @@ --> <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.Type" --> <@compress single_line=true> - <#if wildCardExtendsBound> + <#if hasExtendsBound()> ? extends <@includeModel object=typeBound /> - <#elseif wildCardSuperBound> + <#elseif hasSuperBound()> ? super <@includeModel object=typeBound /> <#else> <#if ext.asVarArgs!false>${createReferenceName()?remove_ending("[]")}...<#else>${createReferenceName()}<#if (!ext.raw?? && typeParameters?size > 0) ><<#list typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > 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 index 96ecd953b9..e857a807af 100644 --- 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 @@ -13,14 +13,19 @@ 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 ( ${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} != null ) { + if ( <#if sourceLocalVarName??>${sourceLocalVarName}<#else>${sourceReference}<#if sourceType.optionalType>.isPresent()<#else> != null ) { <#nested> } <@elseDefaultAssignment/> @@ -40,7 +45,7 @@ } <#elseif setExplicitlyToDefault || setExplicitlyToNull> else { - ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>null; + <#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}; } @@ -57,7 +62,10 @@ --> <#macro handleLocalVarNullCheck needs_explicit_local_var> <#if sourcePresenceCheckerReference??> - 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> @@ -96,11 +104,16 @@ try { <#nested> } - <#list thrownTypes as exceptionType> - catch ( <@includeModel object=exceptionType/> e ) { + <@compress single_line=true>catch ( + <#list thrownTypes as exceptionType> + <#if exceptionType_index > 0> | + <@includeModel object=exceptionType/> + + e ) { + + throw new RuntimeException( e ); } - <#-- @@ -112,6 +125,7 @@ Performs a standard assignment. existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> <#-- @@ -123,6 +137,7 @@ Performs a default assignment with a default value. existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + targetPropertyName=ext.targetPropertyName targetType=ext.targetType defaultValue=ext.defaultValue/> @@ -148,7 +163,7 @@ Performs a default assignment with a default value. <#if factoryMethod??> <@includeModel object=factoryMethod targetType=ext.targetType/> <#else> - <@constructTargetObject/> + <@constructTargetObject targetType=ext.targetType/> <#-- @@ -156,15 +171,18 @@ Performs a default assignment with a default value. purpose: Either call the constructor of the target object directly or of the implementing type. --> -<#macro constructTargetObject><@compress single_line=true> - <#if ext.targetType.implementationType??> - new <@includeModel object=ext.targetType.implementationType/>() - <#elseif ext.targetType.arrayType> - new <@includeModel object=ext.targetType.componentType/>[0] - <#elseif ext.targetType.sensibleDefault??> - ${ext.targetType.sensibleDefault} +<#-- @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/>() + <#elseif targetType.arrayType> + new <@includeModel object=targetType.componentType/>[0] + <#elseif targetType.sensibleDefault??> + ${targetType.sensibleDefault} + <#elseif targetType.optionalType> + <@includeModel object=targetType.asRawType()/>.of( <@constructTargetObject targetType=targetType.optionalBaseType/> ) <#else> - new <@includeModel object=ext.targetType/>() + new <@includeModel object=targetType/>() <#-- @@ -173,6 +191,7 @@ Performs a default assignment with a default value. 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}; 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/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/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/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/internal/model/common/DateFormatValidatorFactoryTest.java index 064aabfd85..646f898063 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 @@ -20,7 +20,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVisitor; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.ap.internal.util.JodaTimeConstants; import org.mapstruct.ap.testutil.IssueKey; @@ -167,7 +167,6 @@ private Type typeWithFQN(String fullQualifiedName) { null, null, null, - null, fullQualifiedName, false, false, @@ -178,7 +177,8 @@ private Type typeWithFQN(String fullQualifiedName) { new HashMap<>( ), new HashMap<>( ), false, - 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 76ceac40df..401a6b2240 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 @@ -20,7 +20,7 @@ import javax.lang.model.type.TypeVisitor; import javax.tools.Diagnostic; -import org.junit.Test; +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; @@ -70,7 +70,7 @@ public void testInvalidDateFormatValidation() { statefulMessagerMock, type, type, - new FormattingParameters( "qwertz", null, null, null, null ) + new FormattingParameters( "qwertz", null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isEqualTo( Diagnostic.Kind.ERROR ); } @@ -84,7 +84,7 @@ public void testNullDateFormatValidation() { statefulMessagerMock, type, type, - new FormattingParameters( null, null, null, null, null ) + new FormattingParameters( null, null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isNull(); } @@ -97,7 +97,7 @@ public void testUnsupportedType() { statefulMessagerMock, type, type, - new FormattingParameters( "qwertz", null, null, null, null ) + new FormattingParameters( "qwertz", null, null, null, null, null ) ); assertThat( statefulMessagerMock.getLastKindPrinted() ).isNull(); } @@ -115,7 +115,6 @@ private Type typeWithFQN(String fullQualifiedName) { null, null, null, - null, fullQualifiedName, false, false, @@ -126,7 +125,8 @@ private Type typeWithFQN(String fullQualifiedName) { new HashMap<>( ), new HashMap<>( ), false, - false); + false, false + ); } private static class StatefulMessagerMock implements FormattingMessager { @@ -162,5 +162,9 @@ public Diagnostic.Kind getLastKindPrinted() { return lastKindPrinted; } + @Override + public boolean isErroneous() { + return false; + } } } 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 index 7eb34135a5..4c6e78e0c1 100644 --- 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 @@ -23,9 +23,9 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVisitor; import javax.lang.model.type.WildcardType; -import javax.lang.model.util.Types; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.mapstruct.ap.internal.util.TypeUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -73,7 +73,12 @@ public String toString() { } } - private final Types typeUtils = new Types() { + 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" ); @@ -174,7 +179,7 @@ public TypeMirror asMemberOf(DeclaredType containing, Element element) { public void testGetters() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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 ); @@ -226,7 +231,7 @@ public void testHashCodeWithNullResultType() { public void testEqualsSameInstance() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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 ); @@ -238,7 +243,7 @@ public void testEqualsSameInstance() { public void testEqualsWitNull() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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 ); @@ -250,7 +255,7 @@ public void testEqualsWitNull() { public void testEqualsQualifiersOneNull() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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 ); @@ -264,12 +269,12 @@ public void testEqualsQualifiersOneNull() { public void testEqualsQualifiersInDifferentOrder() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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(); + 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 ); @@ -282,12 +287,12 @@ public void testEqualsQualifiersInDifferentOrder() { public void testEqualsQualifyingNamesOneNull() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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(); + 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 ); @@ -300,13 +305,13 @@ public void testEqualsQualifyingNamesOneNull() { public void testEqualsQualifyingNamesInDifferentOrder() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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(); + 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 ); @@ -319,13 +324,13 @@ public void testEqualsQualifyingNamesInDifferentOrder() { public void testEqualsResultTypeOneNull() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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(); + 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 ); @@ -338,14 +343,14 @@ public void testEqualsResultTypeOneNull() { public void testAllEqual() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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(); + 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 ); @@ -358,14 +363,14 @@ public void testAllEqual() { public void testDifferentResultTypes() { List qualifyingNames = Arrays.asList( "language", "german" ); TypeMirror resultType = new TestTypeMirror( "resultType" ); - List qualifiers = new ArrayList(); + 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(); + 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 ); 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 index 36f92af6bc..cd96592839 100644 --- a/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/util/NativeTypesTest.java @@ -6,12 +6,13 @@ package org.mapstruct.ap.internal.util; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; import java.math.BigInteger; -import org.junit.Test; + +import org.junit.jupiter.api.Test; /** * @author Ciaran Liedeman @@ -19,7 +20,7 @@ public class NativeTypesTest { @Test - public void testIsNumber() throws Exception { + public void testIsNumber() { assertFalse( NativeTypes.isNumber( null ) ); assertFalse( NativeTypes.isNumber( Object.class ) ); assertFalse( NativeTypes.isNumber( String.class ) ); @@ -121,7 +122,7 @@ public void testIntegerLiteralFromJLS() { .isNotNull(); // most negative int: dec / octal / int / binary - // NOTE parseInt should be changed to parseUnsignedInt in Java, than the - sign can disssapear (java8) + // 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(); @@ -176,7 +177,7 @@ public void testIntegerLiteralFromJLS() { * The following example shows other ways you can use the underscore in numeric literals: */ @Test - public void testFloatingPoingLiteralFromJLS() { + public void testFloatingPointLiteralFromJLS() { // The largest positive finite literal of type float is 3.4028235e38f. assertThat( getLiteral( float.class.getCanonicalName(), "3.4028235e38f" ) ).isNotNull(); 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 index b21db49a3e..524fd193aa 100644 --- a/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/internal/util/StringsTest.java @@ -9,32 +9,17 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Locale; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.junitpioneer.jupiter.DefaultLocale; /** * @author Filip Hrisafov */ public class StringsTest { - private static final Locale TURKEY_LOCALE = getTurkeyLocale(); - private Locale defaultLocale; - - @Before - public void before() { - defaultLocale = Locale.getDefault(); - } - - @After - public void after() { - Locale.setDefault( defaultLocale ); - } - @Test - public void testCapitalize() throws Exception { + public void testCapitalize() { assertThat( Strings.capitalize( null ) ).isNull(); assertThat( Strings.capitalize( "c" ) ).isEqualTo( "C" ); assertThat( Strings.capitalize( "capitalize" ) ).isEqualTo( "Capitalize" ); @@ -43,7 +28,7 @@ public void testCapitalize() throws Exception { } @Test - public void testDecapitalize() throws Exception { + public void testDecapitalize() { assertThat( Strings.decapitalize( null ) ).isNull(); assertThat( Strings.decapitalize( "c" ) ).isEqualTo( "c" ); assertThat( Strings.decapitalize( "capitalize" ) ).isEqualTo( "capitalize" ); @@ -52,14 +37,14 @@ public void testDecapitalize() throws Exception { } @Test - public void testJoin() throws Exception { + 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() throws Exception { + 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" ); @@ -67,7 +52,7 @@ public void testJoinAndCamelize() throws Exception { } @Test - public void testIsEmpty() throws Exception { + public void testIsEmpty() { assertThat( Strings.isEmpty( null ) ).isTrue(); assertThat( Strings.isEmpty( "" ) ).isTrue(); assertThat( Strings.isEmpty( " " ) ).isFalse(); @@ -75,7 +60,7 @@ public void testIsEmpty() throws Exception { } @Test - public void testGetSaveVariableNameWithArrayExistingVariables() throws Exception { + public void testGetSaveVariableNameWithArrayExistingVariables() { assertThat( Strings.getSafeVariableName( "int[]" ) ).isEqualTo( "intArray" ); assertThat( Strings.getSafeVariableName( "Extends" ) ).isEqualTo( "extends1" ); assertThat( Strings.getSafeVariableName( "class" ) ).isEqualTo( "class1" ); @@ -83,31 +68,55 @@ public void testGetSaveVariableNameWithArrayExistingVariables() throws Exception 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() throws Exception { + 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() throws Exception { - assertThat( Strings.getSafeVariableName( "int[]", new ArrayList() ) ).isEqualTo( "intArray" ); - assertThat( Strings.getSafeVariableName( "Extends", new ArrayList() ) ).isEqualTo( "extends1" ); + 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() throws Exception { + 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() throws Exception { + public void findMostSimilarWord() { String mostSimilarWord = Strings.getMostSimilarWord( "fulName", Arrays.asList( "fullAge", "fullName", "address", "status" ) @@ -116,40 +125,31 @@ public void findMostSimilarWord() throws Exception { } @Test + @DefaultLocale("en") public void capitalizeEnglish() { - Locale.setDefault( Locale.ENGLISH ); String international = Strings.capitalize( "international" ); assertThat( international ).isEqualTo( "International" ); } @Test + @DefaultLocale("en") public void decapitalizeEnglish() { - Locale.setDefault( Locale.ENGLISH ); String international = Strings.decapitalize( "International" ); assertThat( international ).isEqualTo( "international" ); } @Test + @DefaultLocale("tr") public void capitalizeTurkish() { - Locale.setDefault( TURKEY_LOCALE ); String international = Strings.capitalize( "international" ); assertThat( international ).isEqualTo( "International" ); } @Test + @DefaultLocale("tr") public void decapitalizeTurkish() { - Locale.setDefault( TURKEY_LOCALE ); String international = Strings.decapitalize( "International" ); assertThat( international ).isEqualTo( "international" ); } - private static Locale getTurkeyLocale() { - Locale turkeyLocale = Locale.forLanguageTag( "tr" ); - - if ( turkeyLocale == null ) { - throw new IllegalStateException( "Can't find Turkey locale." ); - } - - return turkeyLocale; - } } 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 index 02c171e63e..84469f3f64 100644 --- a/processor/src/test/java/org/mapstruct/ap/spi/util/IntrospectorUtilsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/spi/util/IntrospectorUtilsTest.java @@ -5,9 +5,9 @@ */ package org.mapstruct.ap.spi.util; -import static org.assertj.core.api.Assertions.assertThat; +import org.junit.jupiter.api.Test; -import org.junit.Test; +import static org.assertj.core.api.Assertions.assertThat; /** * @author Saheb Preet Singh @@ -15,7 +15,7 @@ public class IntrospectorUtilsTest { @Test - public void testDecapitalize() throws Exception { + public void testDecapitalize() { assertThat( IntrospectorUtils.decapitalize( null ) ).isNull(); assertThat( IntrospectorUtils.decapitalize( "" ) ).isEqualTo( "" ); assertThat( IntrospectorUtils.decapitalize( "URL" ) ).isEqualTo( "URL" ); 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 f510fd32e9..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 @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.abstractclass; -import static org.assertj.core.api.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; /** * Test for the generation of implementation of abstract base classes. @@ -31,10 +29,9 @@ Measurable.class, Holder.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class AbstractClassTest { - @Test + @ProcessorTest @IssueKey("64") public void shouldCreateImplementationOfAbstractMethod() { Source source = new Source(); @@ -42,7 +39,7 @@ public void shouldCreateImplementationOfAbstractMethod() { assertResult( SourceTargetMapper.INSTANCE.sourceToTarget( source ) ); } - @Test + @ProcessorTest @IssueKey("165") public void shouldCreateImplementationOfMethodFromSuper() { Source source = new Source(); @@ -50,7 +47,7 @@ public void shouldCreateImplementationOfMethodFromSuper() { assertResult( SourceTargetMapper.INSTANCE.sourceToTargetFromBaseMapper( source ) ); } - @Test + @ProcessorTest @IssueKey("165") public void shouldCreateImplementationOfMethodFromInterface() { Source source = new Source(); 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 0bde73c975..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 @@ -16,11 +16,7 @@ public int holderToInt(Holder holder) { } 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/Source.java b/processor/src/test/java/org/mapstruct/ap/test/abstractclass/Source.java index f13efc9785..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 @@ -15,7 +15,7 @@ public class Source extends AbstractDto implements HasId, AlsoHasId { 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; 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 index d79f544541..fa483c9037 100644 --- 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 @@ -5,19 +5,17 @@ */ package org.mapstruct.ap.test.abstractclass.generics; -import static org.assertj.core.api.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 org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; /** * @author Andreas Gudian * */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("644,687,688") @WithClasses({ AbstractAnimal.class, @@ -33,7 +31,11 @@ }) public class GenericsHierarchyTest { - @Test + // 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(); @@ -47,7 +49,7 @@ public void determinesAnimalKeyGetter() { assertThat( target.getAnimalKey().typeParameterIsResolvedToKeyOfAllBeings() ).isFalse(); } - @Test + @ProcessorTest public void determinesKeyOfAllBeingsGetter() { AbstractHuman source = new Child(); @@ -60,7 +62,7 @@ public void determinesKeyOfAllBeingsGetter() { assertThat( target.getKeyOfAllBeings().typeParameterIsResolvedToKeyOfAllBeings() ).isTrue(); } - @Test + @ProcessorTest public void determinesItemCSourceSetter() { Target target = new Target(); @@ -72,7 +74,7 @@ public void determinesItemCSourceSetter() { assertThat( source.getKey().typeParameterIsResolvedToAnimalKey() ).isTrue(); } - @Test + @ProcessorTest public void determinesItemBSourceSetter() { Target target = new Target(); 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 0ee789750c..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 @@ -5,16 +5,14 @@ */ 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 @@ -22,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 ); @@ -37,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/DefaultSourceTargetMapperIfc.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/DefaultSourceTargetMapperIfc.java index 84e9aebbbc..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 @@ -12,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/referenced/AbstractSourceTargetMapperPrivate.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/AbstractSourceTargetMapperPrivate.java index fa612bd956..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 @@ -14,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 2ae40488f2..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 @@ -14,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 eb36ad9664..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 @@ -5,16 +5,14 @@ */ package org.mapstruct.ap.test.accessibility.referenced; -import org.junit.Rule; -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; /** @@ -23,13 +21,12 @@ * @author Sjaak Derksen */ @WithClasses( { Source.class, Target.class, ReferencedSource.class, ReferencedTarget.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class ReferencedAccessibilityTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest @IssueKey("206") @WithClasses({ SourceTargetMapperPrivate.class, ReferencedMapperPrivate.class }) @ExpectedCompilationOutcome( @@ -38,26 +35,25 @@ public class ReferencedAccessibilityTest { @Diagnostic(type = SourceTargetMapperPrivate.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 22, - messageRegExp = "Unmapped target property: \"bar\"\\. Mapping from property \"org\\.mapstruct\\.ap\\" + - ".test\\.accessibility\\.referenced\\.ReferencedSource referencedSource\" to \"org\\.mapstruct\\" + - ".ap\\.test\\.accessibility\\.referenced\\.ReferencedTarget referencedTarget\"") + 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 + @ProcessorTest @IssueKey("206") @WithClasses({ SourceTargetMapperDefaultOther.class, ReferencedMapperDefaultOther.class }) @ExpectedCompilationOutcome( @@ -66,35 +62,33 @@ public void shouldBeAbleToAccessProtectedMethodInReferencedInSamePackage() throw @Diagnostic(type = SourceTargetMapperDefaultOther.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 24, - messageRegExp = "Unmapped target property: \"bar\"\\. Mapping from property \"org\\.mapstruct\\.ap\\" + - ".test\\.accessibility\\.referenced\\.ReferencedSource referencedSource\" to \"org\\.mapstruct\\" + - ".ap\\.test\\.accessibility\\.referenced\\.ReferencedTarget referencedTarget\"") + 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 + @ProcessorTest @IssueKey("206") - @WithClasses({ AbstractSourceTargetMapperPrivate.class, SourceTargetmapperPrivateBase.class }) + @WithClasses({ AbstractSourceTargetMapperPrivate.class, SourceTargetMapperPrivateBase.class }) @ExpectedCompilationOutcome( value = CompilationResult.SUCCEEDED, diagnostics = { @Diagnostic(type = AbstractSourceTargetMapperPrivate.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 23, - messageRegExp = "Unmapped target property: \"bar\"\\. Mapping from property \"org\\.mapstruct\\.ap\\" + - ".test\\.accessibility\\.referenced\\.ReferencedSource referencedSource\" to \"org\\.mapstruct\\" + - ".ap\\.test\\.accessibility\\.referenced\\.ReferencedTarget referencedTarget\"") + 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/SourceTargetMapperDefaultOther.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetMapperDefaultOther.java index 4ada977bc8..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 @@ -20,6 +20,6 @@ public interface SourceTargetMapperDefaultOther { 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 36127e1af6..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 @@ -18,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 1ec1aa6c5b..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 @@ -18,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 f8ac1b4d28..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 @@ -18,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 94f9b2668f..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperPrivateBase.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright MapStruct 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/SourceTargetmapperProtectedBase.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java deleted file mode 100644 index 263d74bee6..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/SourceTargetmapperProtectedBase.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright MapStruct 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/a/ReferencedMapperDefaultOther.java b/processor/src/test/java/org/mapstruct/ap/test/accessibility/referenced/a/ReferencedMapperDefaultOther.java index 81dcc306f1..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 @@ -8,7 +8,6 @@ import org.mapstruct.ap.test.accessibility.referenced.ReferencedSource; import org.mapstruct.ap.test.accessibility.referenced.ReferencedTarget; - /** * * @author Sjaak Derksen 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..40c6e60fe6 --- /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 org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +import java.util.List; + +/** + * @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..458cb68d4d --- /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 org.mapstruct.AnnotateWith; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; + +import java.util.Date; +import java.util.Map; + +/** + * @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..c574b59697 --- /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 org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; + +import java.util.stream.Stream; + +/** + * @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..eaa8832df7 --- /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 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 java.lang.reflect.Method; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +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..5abca7171e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/DeprecatedTest.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.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..4e10b6ded6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/annotatewith/deprecated/jdk11/DeprecatedTest.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.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 221bc361a8..67ef2216d8 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 @@ -5,31 +5,28 @@ */ package org.mapstruct.ap.test.array; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.List; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.array._target.ScientistDto; 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.assertj.core.api.Assertions.assertThat; + @WithClasses( { Scientist.class, ScientistDto.class, ScienceMapper.class } ) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("108") public class ArrayMappingTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource() + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() .addComparisonToFixtureFor( ScienceMapper.class ); - @Test + @ProcessorTest public void shouldCopyArraysInBean() { Scientist source = new Scientist("Bob"); @@ -44,7 +41,7 @@ public void shouldCopyArraysInBean() { assertThat( dto.publicPublications ).containsOnly( "public the Lancet", "public Nature" ); } - @Test + @ProcessorTest public void shouldForgeMappingForIntToString() { Scientist source = new Scientist("Bob"); @@ -58,7 +55,7 @@ public void shouldForgeMappingForIntToString() { assertThat( dto.publicPublicationYears ).containsOnly( 1994, 1998 ); } - @Test + @ProcessorTest public void shouldMapArrayToArray() { ScientistDto[] dtos = ScienceMapper.INSTANCE .scientistsToDtos( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); @@ -67,7 +64,7 @@ public void shouldMapArrayToArray() { assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); } - @Test + @ProcessorTest public void shouldMapListToArray() { ScientistDto[] dtos = ScienceMapper.INSTANCE .scientistsToDtos( Arrays.asList( new Scientist( "Bob" ), new Scientist( "Larry" ) ) ); @@ -76,7 +73,7 @@ public void shouldMapListToArray() { assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); } - @Test + @ProcessorTest public void shouldMapArrayToList() { List dtos = ScienceMapper.INSTANCE .scientistsToDtosAsList( new Scientist[]{ new Scientist( "Bob" ), new Scientist( "Larry" ) } ); @@ -85,7 +82,7 @@ public void shouldMapArrayToList() { assertThat( dtos ).extracting( "name" ).containsOnly( "Bob", "Larry" ); } - @Test + @ProcessorTest public void shouldMapArrayToArrayExistingSmallerSizedTarget() { ScientistDto[] existingTarget = new ScientistDto[]{ new ScientistDto( "Jim" ) }; @@ -98,7 +95,7 @@ public void shouldMapArrayToArrayExistingSmallerSizedTarget() { assertThat( target ).extracting( "name" ).containsOnly( "Bob" ); } - @Test + @ProcessorTest public void shouldMapArrayToArrayExistingEqualSizedTarget() { ScientistDto[] existingTarget = new ScientistDto[]{ new ScientistDto( "Jim" ), new ScientistDto( "Bart" ) }; @@ -111,7 +108,7 @@ public void shouldMapArrayToArrayExistingEqualSizedTarget() { assertThat( target ).extracting( "name" ).containsOnly( "Bob", "Larry" ); } - @Test + @ProcessorTest public void shouldMapArrayToArrayExistingLargerSizedTarget() { ScientistDto[] existingTarget = @@ -125,101 +122,108 @@ public void shouldMapArrayToArrayExistingLargerSizedTarget() { 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( 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 ); } - @Test + @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/bool/BooleanMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/bool/BooleanMappingTest.java index 6ca327cce4..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 @@ -5,12 +5,10 @@ */ package org.mapstruct.ap.test.bool; -import static org.assertj.core.api.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({ Person.class, @@ -19,10 +17,9 @@ PersonMapper.class, YesNoMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class BooleanMappingTest { - @Test + @ProcessorTest public void shouldMapBooleanPropertyWithIsPrefixedGetter() { //given Person person = new Person(); @@ -35,7 +32,7 @@ public void shouldMapBooleanPropertyWithIsPrefixedGetter() { assertThat( personDto.getMarried() ).isEqualTo( "true" ); } - @Test + @ProcessorTest public void shouldMapBooleanPropertyPreferringGetPrefixedGetterOverIsPrefixedGetter() { //given Person person = new Person(); @@ -48,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/bugs/_1005/Issue1005Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1005/Issue1005Test.java index b54479a8e4..5198394eb1 100644 --- 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 @@ -5,20 +5,17 @@ */ package org.mapstruct.ap.test.bugs._1005; -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 Filip Hrisafov */ @IssueKey("1005") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ AbstractEntity.class, HasKey.class, @@ -29,52 +26,52 @@ public class Issue1005Test { @WithClasses(Issue1005ErroneousAbstractResultTypeMapper.class) - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = Issue1005ErroneousAbstractResultTypeMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 17, - messageRegExp = "The result type .*\\.AbstractEntity may not be an abstract class nor interface.") + message = "The result type AbstractEntity may not be an abstract class nor interface.") }) - public void shouldFailDueToAbstractResultType() throws Exception { + public void shouldFailDueToAbstractResultType() { } @WithClasses(Issue1005ErroneousAbstractReturnTypeMapper.class) - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = Issue1005ErroneousAbstractReturnTypeMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "The return type .*\\.AbstractEntity is an abstract class or interface. Provide a non" + - " abstract / non interface result type or a factory method.") + 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() throws Exception { + public void shouldFailDueToAbstractReturnType() { } @WithClasses(Issue1005ErroneousInterfaceResultTypeMapper.class) - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = Issue1005ErroneousInterfaceResultTypeMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 17, - messageRegExp = "The result type .*\\.HasPrimaryKey may not be an abstract class nor interface.") + message = "The result type HasPrimaryKey may not be an abstract class nor interface.") }) - public void shouldFailDueToInterfaceResultType() throws Exception { + public void shouldFailDueToInterfaceResultType() { } @WithClasses(Issue1005ErroneousInterfaceReturnTypeMapper.class) - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = Issue1005ErroneousInterfaceReturnTypeMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "The return type .*\\.HasKey is an abstract class or interface. Provide a non " + - "abstract / non interface result type or a factory method.") + 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() throws Exception { + public void shouldFailDueToInterfaceReturnType() { } } 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 index 86c55597d5..c2a87b769a 100644 --- 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 @@ -7,34 +7,31 @@ 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; /** * Verifies that read-only properties can be explicitly mentioned as {@code ignored=true} without raising an error. * * @author Andreas Gudian */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses(ErroneousIssue1029Mapper.class) @IssueKey("1029") public class Issue1029Test { - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(kind = Kind.WARNING, line = 26, type = ErroneousIssue1029Mapper.class, - messageRegExp = "Unmapped target properties: \"knownProp, lastUpdated, computedMapping\"\\."), + message = "Unmapped target properties: \"knownProp, lastUpdated, computedMapping\"."), @Diagnostic(kind = Kind.WARNING, line = 37, type = ErroneousIssue1029Mapper.class, - messageRegExp = "Unmapped target property: \"lastUpdated\"\\."), + message = "Unmapped target property: \"lastUpdated\"."), @Diagnostic(kind = Kind.ERROR, line = 42, type = ErroneousIssue1029Mapper.class, - messageRegExp = "Unknown property \"unknownProp\" in result type " + - "org.mapstruct.ap.test.bugs._1029.ErroneousIssue1029Mapper.Deck\\. Did you mean \"knownProp\"?") + 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 index 09c9d066d2..b060d9727f 100644 --- 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 @@ -5,22 +5,19 @@ */ package org.mapstruct.ap.test.bugs._1061; -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; /** * @author Filip Hrisafov */ @IssueKey("1061") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses(SourceTargetMapper.class) public class Issue1061Test { - @Test - public void shouldCompile() throws Exception { + @ProcessorTest + public void shouldCompile() { } } 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 index a5a624e684..7cdfa82316 100644 --- 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 @@ -24,8 +24,29 @@ public interface Issue1111Mapper { List> listList(List> in); - class Source { } + class Source { + private final String value; - class Target { } + 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 index 623be34f04..f5efb21e3a 100644 --- 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 @@ -5,17 +5,16 @@ */ package org.mapstruct.ap.test.bugs._1111; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; + 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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * @@ -23,13 +22,12 @@ */ @IssueKey( "1111") @WithClasses({Issue1111Mapper.class}) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue1111Test { - @Test + @ProcessorTest public void shouldCompile() { - List> source = Arrays.asList( Arrays.asList( new Source() ) ); + List> source = Arrays.asList( Arrays.asList( new Source( "test" ) ) ); List> target = Issue1111Mapper.INSTANCE.listList( source ); 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 index fce8ffcd6d..cae987aabe 100644 --- 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 @@ -59,6 +59,6 @@ public void setEntity(DTO entity) { class MappingContext { } - @Mapping(source = "entity.id", target = "id") + @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 index 1516c66496..aecc0a3a15 100644 --- 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 @@ -5,26 +5,23 @@ */ package org.mapstruct.ap.test.bugs._1124; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; 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.ap.testutil.runner.AnnotationProcessorTestRunner; import org.mapstruct.factory.Mappers; +import static org.assertj.core.api.Assertions.assertThat; + /** * @author Andreas Gudian */ @IssueKey("1124") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses(Issue1124Mapper.class) public class Issue1124Test { - @Test + @ProcessorTest public void nestedPropertyWithContextCompiles() { Entity entity = new Entity(); 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 index 042d5a2f8b..350d95eebc 100644 --- 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 @@ -30,6 +30,16 @@ public void setB(BEntity b) { } static class BEntity { + + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } } static class ADto { @@ -46,6 +56,7 @@ public void setB(BDto b) { class BDto { private final String passedViaConstructor; + private String id; BDto(String passedViaConstructor) { this.passedViaConstructor = passedViaConstructor; @@ -54,6 +65,14 @@ class BDto { 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); 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 index 2024a83933..d0f8a1d3a4 100644 --- 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 @@ -5,19 +5,17 @@ */ package org.mapstruct.ap.test.bugs._1130; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; 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.ap.testutil.runner.AnnotationProcessorTestRunner; 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. @@ -25,10 +23,9 @@ * @author Andreas Gudian */ @IssueKey("1130") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses(Issue1130Mapper.class) public class Issue1130Test { - @Test + @ProcessorTest public void factoryMethodWithTargetTypeInUpdateMethods() { AEntity aEntity = new AEntity(); aEntity.setB( new BEntity() ); 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 index 46fd66f433..0e82c0708b 100644 --- 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 @@ -20,7 +20,7 @@ public abstract class Issue1131Mapper { public static final Issue1131Mapper INSTANCE = Mappers.getMapper( Issue1131Mapper.class ); - public static final List CALLED_METHODS = new ArrayList(); + public static final List CALLED_METHODS = new ArrayList<>(); public abstract void merge(Source source, @MappingTarget Target target); @@ -40,7 +40,7 @@ protected Target.Nested createWithSource(Source source) { @ObjectFactory protected List createWithSourceList(List source) { CALLED_METHODS.add( "create(List)" ); - List result = new ArrayList(); + 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 index 4939e316e8..3f3176b57a 100644 --- 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 @@ -22,7 +22,7 @@ public abstract class Issue1131MapperWithContext { public static final Issue1131MapperWithContext INSTANCE = Mappers.getMapper( Issue1131MapperWithContext.class ); public static class MappingContext { - private final List calledMethods = new ArrayList(); + private final List calledMethods = new ArrayList<>(); public Target.Nested create(Source.Nested source) { calledMethods.add( "create(Source.Nested)" ); @@ -32,10 +32,10 @@ public Target.Nested create(Source.Nested source) { public List create(List source) { calledMethods.add( "create(List)" ); if ( source == null ) { - return new ArrayList(); + return new ArrayList<>(); } else { - return new ArrayList( source.size() ); + return new ArrayList<>( source.size() ); } } 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 index de3a38366e..7a8c2797d3 100644 --- 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 @@ -7,18 +7,15 @@ import java.util.ArrayList; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1131") @WithClasses({ Issue1131Mapper.class, @@ -28,16 +25,17 @@ }) public class Issue1131Test { - @Test + @ProcessorTest public void shouldUseCreateWithSourceNested() { Source source = new Source(); source.setNested( new Source.Nested() ); source.getNested().setProperty( "something" ); - source.setMoreNested( new ArrayList() ); + source.setMoreNested( new ArrayList<>() ); Target target = new Target(); + Issue1131Mapper.CALLED_METHODS.clear(); Issue1131Mapper.INSTANCE.merge( source, target ); assertThat( target.getNested() ).isNotNull(); @@ -49,13 +47,13 @@ public void shouldUseCreateWithSourceNested() { ); } - @Test + @ProcessorTest public void shouldUseContextObjectFactory() { Source source = new Source(); source.setNested( new Source.Nested() ); source.getNested().setProperty( "something" ); - source.setMoreNested( new ArrayList() ); + source.setMoreNested( new ArrayList<>() ); Target target = new Target(); 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 index fa997becf0..cf2cfdfaf4 100644 --- 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 @@ -19,26 +19,26 @@ public interface Issue1148Mapper { Issue1148Mapper INSTANCE = Mappers.getMapper( Issue1148Mapper.class ); @Mappings({ - @Mapping(source = "senderId", target = "sender.nestedClient.id"), - @Mapping(source = "recipientId", target = "recipient.nestedClient.id"), - @Mapping(source = "sameLevel.client.id", target = "client.nestedClient.id"), - @Mapping(source = "sameLevel2.client.id", target = "client2.nestedClient.id"), - @Mapping(source = "level.client.id", target = "nested.id"), - @Mapping(source = "level2.client.id", target = "nested2.id"), - @Mapping(source = "nestedDto.id", target = "id"), - @Mapping(source = "nestedDto2.id", target = "id2") + @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(source = "dto2.senderId", target = "sender.nestedClient.id"), - @Mapping(source = "dto1.recipientId", target = "recipient.nestedClient.id"), - @Mapping(source = "dto1.sameLevel.client.id", target = "client.nestedClient.id"), - @Mapping(source = "dto2.sameLevel2.client.id", target = "client2.nestedClient.id"), - @Mapping(source = "dto1.level.client.id", target = "nested.id"), - @Mapping(source = "dto2.level2.client.id", target = "nested2.id"), - @Mapping(source = "dto1.nestedDto.id", target = "id"), - @Mapping(source = "dto2.nestedDto2.id", target = "id2") + @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 index 2dc4749b9a..a698cfbfc6 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1148; -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; @@ -20,12 +18,11 @@ Entity.class, Issue1148Mapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1148") public class Issue1148Test { - @Test - public void shouldNotUseSameMethodForDifferentMappingsNestedSource() throws Exception { + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsNestedSource() { Entity.Dto dto = new Entity.Dto(); dto.nestedDto = new Entity.NestedDto( 30 ); dto.nestedDto2 = new Entity.NestedDto( 40 ); @@ -35,8 +32,8 @@ public void shouldNotUseSameMethodForDifferentMappingsNestedSource() throws Exce assertThat( entity.getId2() ).isEqualTo( 40 ); } - @Test - public void shouldNotUseSameMethodForDifferentMappingsNestedTarget() throws Exception { + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsNestedTarget() { Entity.Dto dto = new Entity.Dto(); dto.recipientId = 10; dto.senderId = 20; @@ -51,8 +48,8 @@ public void shouldNotUseSameMethodForDifferentMappingsNestedTarget() throws Exce assertThat( entity.getSender().nestedClient.id ).isEqualTo( 20 ); } - @Test - public void shouldNotUseSameMethodForDifferentMappingsSymmetric() throws Exception { + @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 )); @@ -67,8 +64,8 @@ public void shouldNotUseSameMethodForDifferentMappingsSymmetric() throws Excepti assertThat( entity.client2.nestedClient.id ).isEqualTo( 40 ); } - @Test - public void shouldNotUseSameMethodForDifferentMappingsHalfSymmetric() throws Exception { + @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 )); @@ -81,8 +78,8 @@ public void shouldNotUseSameMethodForDifferentMappingsHalfSymmetric() throws Exc assertThat( entity.nested2.id ).isEqualTo( 90 ); } - @Test - public void shouldNotUseSameMethodForDifferentMappingsNestedSourceMultiple() throws Exception { + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsNestedSourceMultiple() { Entity.Dto dto1 = new Entity.Dto(); dto1.nestedDto = new Entity.NestedDto( 30 ); Entity.Dto dto2 = new Entity.Dto(); @@ -93,8 +90,8 @@ public void shouldNotUseSameMethodForDifferentMappingsNestedSourceMultiple() thr assertThat( entity.getId2() ).isEqualTo( 40 ); } - @Test - public void shouldNotUseSameMethodForDifferentMappingsNestedTargetMultiple() throws Exception { + @ProcessorTest + public void shouldNotUseSameMethodForDifferentMappingsNestedTargetMultiple() { Entity.Dto dto1 = new Entity.Dto(); dto1.recipientId = 10; Entity.Dto dto2 = new Entity.Dto(); @@ -110,8 +107,8 @@ public void shouldNotUseSameMethodForDifferentMappingsNestedTargetMultiple() thr assertThat( entity.getSender().nestedClient.id ).isEqualTo( 20 ); } - @Test - public void shouldNotUseSameMethodForDifferentMappingsSymmetricMultiple() throws Exception { + @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(); @@ -127,7 +124,7 @@ public void shouldNotUseSameMethodForDifferentMappingsSymmetricMultiple() throws assertThat( entity.client2.nestedClient.id ).isEqualTo( 40 ); } - @Test + @ProcessorTest public void shouldNotUseSameMethodForDifferentMappingsHalfSymmetricMultiple() { Entity.Dto dto1 = new Entity.Dto(); dto1.level = new Entity.ClientDto(new Entity.NestedDto( 80 )); 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 index 3ed3e20db1..5ceb1bb715 100644 --- 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 @@ -5,19 +5,16 @@ */ package org.mapstruct.ap.test.bugs._1153; -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 Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses(ErroneousIssue1153Mapper.class) @IssueKey("1153") public class Issue1153Test { @@ -27,21 +24,20 @@ public class Issue1153Test { @Diagnostic(type = ErroneousIssue1153Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 19, - messageRegExp = "Property \"readOnly\" has no write accessor in " + - "org.mapstruct.ap.test.bugs._1153.ErroneousIssue1153Mapper.Target\\."), + message = "Property \"readOnly\" has no write accessor in ErroneousIssue1153Mapper.Target."), @Diagnostic(type = ErroneousIssue1153Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, - messageRegExp = "Property \"nestedTarget.readOnly\" has no write accessor in " + - "org.mapstruct.ap.test.bugs._1153.ErroneousIssue1153Mapper.Target\\."), + 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, - messageRegExp = "Unknown property \"nestedTarget2.writable2\" in result type " + - "org.mapstruct.ap.test.bugs._1153.ErroneousIssue1153Mapper.Target\\. " + - "Did you mean \"nestedTarget2\\.writable\"") + message = "Unknown property \"writable2\" in type ErroneousIssue1153Mapper.Target.NestedTarget " + + "for target name \"nestedTarget2.writable2\". Did you mean \"nestedTarget2.writable\"?") }) - @Test + @ProcessorTest public void shouldReportErrorsCorrectly() { } } 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 index cb2fd3453c..072121bf79 100644 --- 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 @@ -17,6 +17,6 @@ public interface Issue1155Mapper { Issue1155Mapper INSTANCE = Mappers.getMapper( Issue1155Mapper.class ); - @Mapping(source = "clientId", target = "client.id") + @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 index a3295fe4df..9a6bb40ed1 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1155; -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; @@ -20,12 +18,11 @@ Entity.class, Issue1155Mapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1155") public class Issue1155Test { - @Test - public void shouldCompile() throws Exception { + @ProcessorTest + public void shouldCompile() { Entity.Dto dto = new Entity.Dto(); dto.clientId = 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/Issue1164Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1164/Issue1164Test.java index 7fa5a343ad..6db73113b6 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1164; -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; /** * @author Filip Hrisafov @@ -20,11 +18,10 @@ GenericHolder.class, SourceTargetMapper.class } ) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey( "1164" ) public class Issue1164Test { - @Test + @ProcessorTest public void shouldCompile() { } } 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 index 41d62c30de..31cb10ba6f 100644 --- 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 @@ -21,14 +21,14 @@ public abstract class SourceTargetMapper { public abstract Target map(Source source); protected List> mapLists(List> lists) { - return new ArrayList>(); + return new ArrayList<>(); } protected Map> map(Map> map) { - return new HashMap>(); + return new HashMap<>(); } protected GenericHolder> map(GenericHolder> genericHolder) { - return new GenericHolder>(); + return new GenericHolder<>(); } } 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 index b68884e0e3..88576ba711 100644 --- 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 @@ -5,18 +5,16 @@ */ package org.mapstruct.ap.test.bugs._1170; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import org.assertj.core.api.ListAssert; -import org.junit.Test; -import org.junit.runner.RunWith; 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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * @author Cornelius Dirmeier @@ -27,11 +25,10 @@ AdderSourceTargetMapper.class, PetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class AdderTest { @IssueKey("1170") - @Test + @ProcessorTest public void testWildcardAdder() { Source source = new Source(); source.addWithoutWildcard( "mouse" ); @@ -51,7 +48,7 @@ public void testWildcardAdder() { } @IssueKey("1170") - @Test + @ProcessorTest public void testWildcardAdderTargetToSource() { Target target = new Target(); target.addWithoutWildcard( 2L ); 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 index f76cd4e91b..96b7e8b72f 100644 --- 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 @@ -5,9 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1170; -import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.mapstruct.Mapper; @@ -29,15 +29,12 @@ public class PetMapper { .put( 3L, "cat" ) .put( 4L, "dog" ).build(); - /** * method to be used when using an adder * * @param pet * * @return - * - * @throws DogException */ public Long toPet(String pet) { return PETS_TO_TARGET.get( pet ); @@ -53,24 +50,17 @@ public String toSourcePets(Long pet) { * @param pets * * @return - * - * @throws CatException - * @throws DogException */ public List toPets(List pets) { - List result = new ArrayList(); - for ( String pet : pets ) { - result.add( toPet( pet ) ); - } - return result; + return pets.stream() + .map( this::toPet ) + .collect( Collectors.toList() ); } public List toSourcePets(List pets) { - List result = new ArrayList(); - for ( Long pet : pets ) { - result.add( PETS_TO_SOURCE.get( pet ) ); - } - return result; + 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 index 95e0414f2a..5103fb075a 100644 --- 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 @@ -14,23 +14,23 @@ */ public class Target { - private List withoutWildcards = new ArrayList(); + private List withoutWildcards = new ArrayList<>(); - private List wildcardInSources = new ArrayList(); + private List wildcardInSources = new ArrayList<>(); - private List wildcardInTargets = new ArrayList(); + private List wildcardInTargets = new ArrayList<>(); - private List wildcardInBoths = new ArrayList(); + private List wildcardInBoths = new ArrayList<>(); - private List wildcardAdderToSetters = new ArrayList(); + private List wildcardAdderToSetters = new ArrayList<>(); - private List wildcardInSourcesAddAll = new ArrayList(); + private List wildcardInSourcesAddAll = new ArrayList<>(); - private List sameTypeWildcardInSources = new ArrayList(); + private List sameTypeWildcardInSources = new ArrayList<>(); - private List sameTypeWildcardInTargets = new ArrayList(); + private List sameTypeWildcardInTargets = new ArrayList<>(); - private List sameTypeWildcardInBoths = new ArrayList(); + private List sameTypeWildcardInBoths = new ArrayList<>(); public List getWithoutWildcards() { return withoutWildcards; 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 index 4226c92455..989ef5f6b9 100644 --- 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 @@ -14,23 +14,23 @@ */ public class Source { - private List withoutWildcards = new ArrayList(); + private List withoutWildcards = new ArrayList<>(); - private List wildcardInSources = new ArrayList(); + private List wildcardInSources = new ArrayList<>(); - private List wildcardInTargets = new ArrayList(); + private List wildcardInTargets = new ArrayList<>(); - private List wildcardInBoths = new ArrayList(); + private List wildcardInBoths = new ArrayList<>(); - private List wildcardInSourcesAddAll = new ArrayList(); + private List wildcardInSourcesAddAll = new ArrayList<>(); - private List wildcardAdderToSetters = new ArrayList(); + private List wildcardAdderToSetters = new ArrayList<>(); - private List sameTypeWildcardInSources = new ArrayList(); + private List sameTypeWildcardInSources = new ArrayList<>(); - private List sameTypeWildcardInTargets = new ArrayList(); + private List sameTypeWildcardInTargets = new ArrayList<>(); - private List sameTypeWildcardInBoths = new ArrayList(); + private List sameTypeWildcardInBoths = new ArrayList<>(); public List getWithoutWildcards() { return withoutWildcards; 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 index 80910e55aa..865816d83e 100644 --- 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 @@ -5,14 +5,12 @@ */ package org.mapstruct.ap.test.bugs._1180; -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 @@ -23,16 +21,15 @@ SharedConfig.class, ErroneousIssue1180Mapper.class } ) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey( "1180" ) public class Issue1180Test { - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = SharedConfig.class, + @Diagnostic(type = ErroneousIssue1180Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 20, + line = 18, messageRegExp = "No property named \"sourceProperty\\.nonExistant\" exists.*") }) public void shouldCompileButNotGiveNullPointer() { 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 index c18a858d47..e3c08488a0 100644 --- 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 @@ -5,16 +5,14 @@ */ package org.mapstruct.ap.test.bugs._1215; -import org.junit.Test; -import org.junit.runner.RunWith; 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; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * @author Filip Hrisafov @@ -27,10 +25,9 @@ Issue1215Mapper.class } ) @IssueKey( "1215" ) -@RunWith( AnnotationProcessorTestRunner.class ) public class Issue1215Test { - @Test + @ProcessorTest public void shouldCompile() { } } 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 index e4612e9d87..f98f6de398 100644 --- 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 @@ -5,24 +5,21 @@ */ package org.mapstruct.ap.test.bugs._1227; -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; /** * @author Filip Hrisafov */ @IssueKey("1227") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Issue1227Mapper.class, ThreadDto.class }) public class Issue1227Test { - @Test + @ProcessorTest public void shouldCompile() { } } 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 index 7259d343b8..7a8ad05542 100644 --- 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 @@ -7,13 +7,14 @@ 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) +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class ErroneousIssue1242MapperMultipleSources { abstract TargetA toTargetA(SourceA source); 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 index 0491b5ffe0..90ea60a2ee 100644 --- 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 @@ -7,13 +7,14 @@ 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) +@Mapper(uses = TargetFactories.class, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class Issue1242Mapper { abstract TargetA toTargetA(SourceA source); 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 index 94a7e3bbae..036a63f312 100644 --- 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 @@ -5,18 +5,16 @@ */ package org.mapstruct.ap.test.bugs._1242; -import static org.assertj.core.api.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.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.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. @@ -24,7 +22,6 @@ * @author Andreas Gudian */ @IssueKey("1242") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Issue1242Mapper.class, SourceA.class, @@ -34,7 +31,7 @@ TargetFactories.class }) public class Issue1242Test { - @Test + @ProcessorTest public void factoryMethodWithSourceParamIsChosen() { SourceA sourceA = new SourceA(); sourceA.setB( new SourceB() ); @@ -51,23 +48,19 @@ public void factoryMethodWithSourceParamIsChosen() { assertThat( targetA.getB().getPassedViaConstructor() ).isEqualTo( "created by factory" ); } - @Test + @ProcessorTest @WithClasses(ErroneousIssue1242MapperMultipleSources.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = ErroneousIssue1242MapperMultipleSources.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 20, - messageRegExp = "Ambiguous factory methods found for creating .*TargetB:" - + " .*TargetB anotherTargetBCreator\\(.*SourceB source\\)," - + " .*TargetB .*TargetFactories\\.createTargetB\\(.*SourceB source," - + " @TargetType .*Class<.*TargetB> clazz\\)," - + " .*TargetB .*TargetFactories\\.createTargetB\\(@TargetType java.lang.Class<.*TargetB> clazz\\)," - + " .*TargetB .*TargetFactories\\.createTargetB\\(\\)."), - @Diagnostic(type = ErroneousIssue1242MapperMultipleSources.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 20, - messageRegExp = ".*TargetB does not have an accessible parameterless constructor\\.") + 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/TargetB.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1242/TargetB.java index ab4f639697..6a28adc828 100644 --- 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 @@ -9,6 +9,7 @@ * @author Andreas Gudian */ class TargetB { + protected String value; private final String passedViaConstructor; TargetB(String passedViaConstructor) { @@ -18,4 +19,12 @@ class TargetB { 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/_1244/Issue1244Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1244/Issue1244Test.java index ebb27fc202..231ff67334 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1244; -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.assertj.core.api.Assertions.assertThat; @@ -18,11 +16,10 @@ * @author Filip Hrisafov */ @IssueKey("1244") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses( SizeMapper.class ) public class Issue1244Test { - @Test + @ProcessorTest public void properlyCreatesMapperWithSizeAsParameterName() { SizeMapper.SizeHolder sizeHolder = new SizeMapper.SizeHolder(); sizeHolder.setSize( "size" ); 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 index 884fb7fdb2..1dd12d6e23 100644 --- 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 @@ -8,11 +8,9 @@ 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; import static org.assertj.core.api.Assertions.assertThat; @@ -20,7 +18,6 @@ * @author Filip Hrisafov */ @IssueKey("1247") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Issue1247Mapper.class, DtoIn.class, @@ -33,7 +30,7 @@ }) public class Issue1247Test { - @Test + @ProcessorTest public void shouldCorrectlyUseMappings() { DtoIn in = new DtoIn( "data", "data2" ); @@ -48,7 +45,7 @@ public void shouldCorrectlyUseMappings() { assertThat( out.getInternal().getInternalData().getList() ).containsExactly( "first", "second" ); } - @Test + @ProcessorTest public void shouldCorrectlyUseMappingsWithConstantsExpressionsAndDefaults() { DtoIn in = new DtoIn( "data", "data2" ); @@ -68,7 +65,7 @@ public void shouldCorrectlyUseMappingsWithConstantsExpressionsAndDefaults() { assertThat( out.getInternal().getInternalData().getDefaultValue() ).isEqualTo( "data2" ); } - @Test + @ProcessorTest public void shouldCorrectlyUseMappingsWithConstantsExpressionsAndUseDefault() { DtoIn in = new DtoIn( "data", null ); 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 index a459099000..9d27fdf681 100644 --- 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 @@ -5,19 +5,17 @@ */ package org.mapstruct.ap.test.bugs._1255; -import static org.assertj.core.api.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; /** * * @author Sjaak Derksen */ @IssueKey("1255") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ AbstractA.class, SomeA.class, @@ -26,8 +24,8 @@ SomeMapperConfig.class}) public class Issue1255Test { - @Test - public void shouldMapSomeBToSomeAWithoutField1() throws Exception { + @ProcessorTest + public void shouldMapSomeBToSomeAWithoutField1() { SomeB someB = new SomeB(); someB.setField1( "value1" ); someB.setField2( "value2" ); @@ -40,8 +38,8 @@ public void shouldMapSomeBToSomeAWithoutField1() throws Exception { assertThat( someA.getField2() ).isEqualTo( someB.getField2() ); } - @Test - public void shouldMapSomeAToSomeB() throws Exception { + @ProcessorTest + public void shouldMapSomeAToSomeB() { SomeA someA = new SomeA(); someA.setField1( "value1" ); someA.setField2( "value2" ); 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 index 7f44ea012b..668b9e5110 100644 --- 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 @@ -8,8 +8,6 @@ import java.util.Arrays; import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; 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; @@ -18,8 +16,8 @@ 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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -27,7 +25,6 @@ * @author Filip Hrisafov */ @IssueKey( "1269" ) -@RunWith( AnnotationProcessorTestRunner.class ) @WithClasses( { VehicleDto.class, VehicleImageDto.class, @@ -39,7 +36,7 @@ } ) public class Issue1269Test { - @Test + @ProcessorTest public void shouldMapNestedPropertiesCorrectly() { VehicleTypeInfo sourceTypeInfo = new VehicleTypeInfo( "Opel", "Corsa", 3 ); 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 index ece26b3bdd..fa1c1d47f5 100644 --- 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 @@ -10,7 +10,7 @@ public class Entity { - List longs = new ArrayList(); + List longs = new ArrayList<>(); public List getLongs() { return longs; 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 index e12edd50a2..9874b33436 100644 --- 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 @@ -5,21 +5,18 @@ */ package org.mapstruct.ap.test.bugs._1273; -import static org.assertj.core.api.AssertionsForInterfaceTypes.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 org.mapstruct.factory.Mappers; +import static org.assertj.core.api.Assertions.assertThat; + @IssueKey( "1273" ) -@RunWith( AnnotationProcessorTestRunner.class ) @WithClasses( { EntityMapperReturnDefault.class, EntityMapperReturnNull.class, Dto.class, Entity.class } ) public class Issue1273Test { - @Test + @ProcessorTest public void shouldCorrectlyMapCollectionWithNullValueMappingStrategyReturnDefault() { EntityMapperReturnDefault entityMapper = Mappers.getMapper( EntityMapperReturnDefault.class ); @@ -29,7 +26,7 @@ public void shouldCorrectlyMapCollectionWithNullValueMappingStrategyReturnDefaul assertThat( dto.getLongs() ).isNotNull(); } - @Test + @ProcessorTest public void shouldCorrectlyMapCollectionWithNullValueMappingStrategyReturnNull() { EntityMapperReturnNull entityMapper = Mappers.getMapper( EntityMapperReturnNull.class ); 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 index 9e33e2f079..1879c9398b 100644 --- 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 @@ -5,27 +5,24 @@ */ package org.mapstruct.ap.test.bugs._1283; -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 Filip Hrisafov */ @IssueKey("1283") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Source.class, Target.class }) public class Issue1283Test { - @Test + @ProcessorTest @WithClasses(ErroneousInverseTargetHasNoSuitableConstructorMapper.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -33,14 +30,14 @@ public class Issue1283Test { @Diagnostic(type = ErroneousInverseTargetHasNoSuitableConstructorMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 22L, - messageRegExp = ".*\\._1283\\.Source does not have an accessible parameterless constructor" + message = "Source does not have an accessible constructor." ) } ) public void inheritInverseConfigurationReturnTypeHasNoSuitableConstructor() { } - @Test + @ProcessorTest @WithClasses(ErroneousTargetHasNoSuitableConstructorMapper.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -48,7 +45,7 @@ public void inheritInverseConfigurationReturnTypeHasNoSuitableConstructor() { @Diagnostic(type = ErroneousTargetHasNoSuitableConstructorMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 18L, - messageRegExp = ".*\\._1283\\.Source does not have an accessible parameterless constructor" + message = "Source does not have an accessible constructor." ) } ) 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 index a34c8d4a53..227296ad25 100644 --- 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 @@ -12,7 +12,7 @@ public class Source { private String source; - public Source(String source) { + private Source(String source) { this.source = source; } 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 index 2b6b918bb3..b84fe2d8e6 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1320; -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; @@ -17,14 +15,13 @@ * @author Filip Hrisafov */ @IssueKey("1320") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Issue1320Mapper.class, Target.class }) public class Issue1320Test { - @Test + @ProcessorTest public void shouldCreateDeepNestedConstantsCorrectly() { Target target = Issue1320Mapper.INSTANCE.map( 10 ); 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 index 8274b748ae..924f5e8c86 100644 --- 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 @@ -7,19 +7,15 @@ 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.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.atIndex; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1338") @WithClasses({ Issue1338Mapper.class, @@ -28,15 +24,14 @@ }) public class Issue1338Test { - @Test + @ProcessorTest public void shouldCorrectlyUseAdder() { Source source = new Source(); source.setProperties( Arrays.asList( "first", "second" ) ); Target target = Issue1338Mapper.INSTANCE.map( source ); - assertThat( target ) - .extracting( "properties" ) - .contains( Arrays.asList( "first", "second" ), atIndex( 0 ) ); + assertThat( target.getProperties() ) + .containsExactly( "first", "second" ); Source mapped = Issue1338Mapper.INSTANCE.map( target ); 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 index 4717001147..f5157b9690 100644 --- 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 @@ -17,7 +17,7 @@ public class Source { public void addProperty(String property) { if ( properties == null ) { - properties = new ArrayList(); + properties = new ArrayList<>(); } properties.add( property ); } 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 index 5846860098..fa1caff745 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1339; -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; @@ -20,11 +18,10 @@ Issue1339Mapper.class, Callback.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1339") public class Issue1339Test { - @Test + @ProcessorTest public void shouldCompile() { Issue1339Mapper.Source source = new Issue1339Mapper.Source(); source.field = "test"; 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 index d4fdac257d..3135dfff67 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1340; -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; /** * @author Filip Hrisafov @@ -18,10 +16,9 @@ Issue1340Mapper.class }) @IssueKey("1340") -@RunWith(AnnotationProcessorTestRunner.class) public class Issue1340Test { - @Test + @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 index 7699a8f7e9..b2d89c96e6 100644 --- 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 @@ -27,8 +27,17 @@ public interface Issue1345Mapper { 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; } @@ -36,8 +45,17 @@ public String getReadOnlyProperty() { 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; } 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 index c5ad1c3030..7be9b0edc1 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1345; -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; /** * @author Sjaak Derksen @@ -18,10 +16,9 @@ Issue1345Mapper.class }) @IssueKey("1345") -@RunWith(AnnotationProcessorTestRunner.class) public class Issue1345Test { - @Test + @ProcessorTest public void shouldCompile() { } } 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 index da89288013..4398d6ebcd 100644 --- 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 @@ -5,14 +5,12 @@ */ package org.mapstruct.ap.test.bugs._1353; -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; @@ -20,7 +18,6 @@ * @author Jeffrey Smyth */ @IssueKey ("1353") -@RunWith (AnnotationProcessorTestRunner.class) @WithClasses ({ Issue1353Mapper.class, Source.class, @@ -28,20 +25,20 @@ }) public class Issue1353Test { - @Test + @ProcessorTest @ExpectedCompilationOutcome ( value = CompilationResult.SUCCEEDED, diagnostics = { @Diagnostic (type = Issue1353Mapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 22, - messageRegExp = "The property named \" source.string1\" has whitespaces," + 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, - messageRegExp = "The property named \"string2 \" has whitespaces," + message = "The property named \"string2 \" has whitespaces," + " using trimmed property \"string2\" instead." ) } 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 index d81554b8eb..a412cd19f6 100644 --- 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 @@ -8,14 +8,12 @@ import java.util.HashSet; 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; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.atIndex; +import static org.assertj.core.api.InstanceOfAssertFactories.ITERABLE; /** * @author Filip Hrisafov @@ -25,22 +23,21 @@ Source.class, Target.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) @IssueKey( "1359" ) public class Issue1359Test { - @Test + @ProcessorTest public void shouldCompile() { Target target = new Target(); - assertThat( target ).extracting( "properties" ).contains( null, atIndex( 0 ) ); + assertThat( target ).extracting( "properties" ).isNull(); - Set properties = new HashSet(); + Set properties = new HashSet<>(); properties.add( "first" ); Source source = new Source( properties ); Issue1359Mapper.INSTANCE.map( target, source ); - assertThat( target ).extracting( "properties" ).contains( properties, atIndex( 0 ) ); + assertThat( target ).extracting( "properties", ITERABLE ).containsExactly( "first" ); } } 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 index d08d8d3d36..9dcb9655f4 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1375; -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; @@ -21,11 +19,10 @@ Source.class, Issue1375Mapper.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) @IssueKey( "1375" ) public class Issue1375Test { - @Test + @ProcessorTest public void shouldGenerateCorrectMapperWhenIntermediaryReadAccessorIsMissing() { Target target = Issue1375Mapper.INSTANCE.map( new Source( "test value" ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java deleted file mode 100644 index 559e39512e..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Mapper.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * 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; - -import org.mapstruct.InjectionStrategy; -import org.mapstruct.Mapper; - -/** - * @author Filip Hrisafov - */ -@Mapper(injectionStrategy = InjectionStrategy.CONSTRUCTOR, 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/Issue1395Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java deleted file mode 100644 index 21d2f5015b..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Issue1395Test.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * 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; - -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; - -/** - * @author Filip Hrisafov - */ -@WithClasses( { - Issue1395Mapper.class, - NotUsedService.class, - Source.class, - Target.class -} ) -@RunWith( AnnotationProcessorTestRunner.class ) -@IssueKey( "1395" ) -public class Issue1395Test { - - @Test - public void shouldGenerateValidCode() { - - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java deleted file mode 100644 index 1b676c2b62..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Source.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * 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; - -/** - * @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/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java deleted file mode 100644 index 2c7b22fd4d..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/Target.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * 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; - -/** - * @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/_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/NotUsedService.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java similarity index 81% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java index 765764ef2b..ba47cf179f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/NotUsedService.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1395/spring/NotUsedService.java @@ -3,7 +3,7 @@ * * 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; +package org.mapstruct.ap.test.bugs._1395.spring; /** * @author Filip Hrisafov 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/Issue1425Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1425/Issue1425Test.java index 25a148e474..a71c33c396 100644 --- 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 @@ -6,11 +6,10 @@ package org.mapstruct.ap.test.bugs._1425; import org.joda.time.LocalDate; -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.ap.testutil.WithJoda; import static org.assertj.core.api.Assertions.assertThat; @@ -22,11 +21,11 @@ Source.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1425") +@WithJoda public class Issue1425Test { - @Test + @ProcessorTest public void shouldTestMappingLocalDates() { Source source = new Source(); source.setValue( LocalDate.parse( "2018-04-18" ) ); 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 index ade951f2b1..2fa173cac1 100644 --- 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 @@ -5,15 +5,12 @@ */ package org.mapstruct.ap.test.bugs._1435; -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; -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1435") @WithClasses({ Config.class, @@ -22,7 +19,7 @@ OutObject.class, }) public class Issue1435Test { - @Test + @ProcessorTest public void mustNotSetListToNull() { InObject source = new InObject( "Rainbow Dash" ); 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 index ef92bc8272..5b8fd42a25 100644 --- 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 @@ -24,7 +24,7 @@ List takePayments() { } public void setPayments(List payments) { - this.payments = payments == null ? null : new ArrayList( payments ); + this.payments = payments == null ? null : new ArrayList<>( payments ); } List takeOtherPayments() { @@ -40,7 +40,7 @@ public void setOtherPayments(List otherPayments) { } public void setMapPayments(Map mapPayments) { - this.mapPayments = mapPayments == null ? null : new HashMap( mapPayments ); + this.mapPayments = mapPayments == null ? null : new HashMap<>( mapPayments ); } public Map getMapSuperPayments() { 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 index 9ba0a41cf3..d112df0355 100644 --- 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 @@ -7,12 +7,10 @@ import java.util.Arrays; -import org.junit.Rule; -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; @@ -21,7 +19,6 @@ * @author Filip Hrisafov */ @IssueKey("1453") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Auction.class, AuctionDto.class, @@ -31,10 +28,10 @@ }) public class Issue1453Test { - @Rule - public GeneratedSource source = new GeneratedSource().addComparisonToFixtureFor( Issue1453Mapper.class ); + @RegisterExtension + final GeneratedSource source = new GeneratedSource().addComparisonToFixtureFor( Issue1453Mapper.class ); - @Test + @ProcessorTest public void shouldGenerateCorrectCode() { AuctionDto target = Issue1453Mapper.INSTANCE.map( new Auction( 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 index 6d30ce345d..de1160771a 100644 --- 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 @@ -5,15 +5,13 @@ */ package org.mapstruct.ap.test.bugs._1457; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -21,7 +19,6 @@ SourceBook.class, TargetBook.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1457") public class Issue1457Test { @@ -29,7 +26,7 @@ public class Issue1457Test { private String authorFirstName; private String authorLastName; - @Before + @BeforeEach public void setup() { sourceBook = new SourceBook(); sourceBook.setIsbn( "3453146972" ); @@ -39,18 +36,19 @@ public void setup() { authorLastName = "Adams"; } - @Test + @ProcessorTest @WithClasses({ BookMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.SUCCEEDED, diagnostics = @Diagnostic( - messageRegExp = - "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 '.*\\.TargetBook mapBook\\(" + - ".*\\.SourceBook sourceBook, .*\\.String authorFirstName, .*\\.String authorLastName\\)'\\.", + 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 ) @@ -66,7 +64,7 @@ public void testMapperWithMatchingParameterNames() { assertThat( targetBook.isAfterMappingWithDifferentVariableName() ).isFalse(); } - @Test + @ProcessorTest @WithClasses({ DifferentOrderingBookMapper.class }) @@ -80,7 +78,7 @@ public void testMapperWithMatchingParameterNamesAndDifferentOrdering() { assertTargetBookMatchesSourceBook( targetBook ); } - @Test + @ProcessorTest @WithClasses({ ObjectFactoryBookMapper.class }) @@ -101,23 +99,23 @@ private void assertTargetBookMatchesSourceBook(TargetBook targetBook) { assertThat( authorLastName ).isEqualTo( targetBook.getAuthorLastName() ); } - @Test + @ProcessorTest @WithClasses({ ErroneousBookMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.SUCCEEDED, diagnostics = @Diagnostic( - messageRegExp = - "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 '.*\\.TargetBook mapBook\\(" + - ".*\\.SourceBook sourceBook, .*\\.String authorFirstName, .*\\.String authorLastName\\)'\\.", + 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/_1460/Issue1460Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1460/Issue1460Mapper.java index 90ed83d28a..1d0d4fccbb 100644 --- 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 @@ -18,13 +18,13 @@ public abstract class Issue1460Mapper { public abstract Target map(Source source); - public abstract String forceUsageOfIssue1460Enum(Issue1460Enum source); + public abstract Value forceUsageOfIssue1460Enum(Issue1460Enum source); - public abstract String forceUsageOfLocale(Locale source); + public abstract Value forceUsageOfLocale(Locale source); - public abstract String forceUsageOfLocalDate(LocalDate source); + public abstract Value forceUsageOfLocalDate(LocalDate source); - public abstract String forceUsageOfDateTime(DateTime source); + public abstract Value forceUsageOfDateTime(DateTime source); public static class Issue1460Enum { } @@ -37,4 +37,17 @@ 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 index b436899147..e9c97f39bc 100644 --- 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 @@ -9,11 +9,10 @@ import java.util.Locale; import org.joda.time.DateTime; -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.ap.testutil.WithJoda; import static org.assertj.core.api.Assertions.assertThat; @@ -25,11 +24,11 @@ Source.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1460") +@WithJoda public class Issue1460Test { - @Test + @ProcessorTest public void shouldTestMappingLocalDates() { long dateInMs = 1524693600000L; String dateAsString = "2018-04-26"; 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 index 216cda21fd..b9bedfe451 100644 --- 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 @@ -18,8 +18,17 @@ public abstract class Issue1460JavaTimeMapper { public abstract Target map(Source source); - public abstract String forceUsageOfLocalDate(LocalDate 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 index ff3ea2d1c7..829402efaa 100644 --- 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 @@ -7,11 +7,9 @@ import java.time.LocalDate; -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; @@ -23,11 +21,10 @@ Source.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1460") public class Issue1460JavaTimeTest { - @Test + @ProcessorTest public void shouldTestMappingLocalDates() { String dateAsString = "2018-04-26"; 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 index 74f6923734..d59b58dbda 100644 --- 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 @@ -7,11 +7,9 @@ import java.math.BigDecimal; -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; @@ -20,16 +18,14 @@ Source2.class, Target.class, SourceEnum.class, - SourceTargetMapper.class, - TargetSourceMapper.class, BigDecimalWrapper.class, ValueWrapper.class }) @IssueKey(value = "1482") -@RunWith(AnnotationProcessorTestRunner.class) public class Issue1482Test { - @Test + @ProcessorTest + @WithClasses( SourceTargetMapper.class ) public void testForward() { Source source = new Source(); @@ -44,7 +40,8 @@ public void testForward() { } - @Test + @ProcessorTest + @WithClasses( TargetSourceMapper.class ) public void testReverse() { Target target = new Target(); 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 index d589927a19..401d0c42ae 100644 --- 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 @@ -20,9 +20,9 @@ public abstract class TargetSourceMapper { @Mapping(target = "wrapper", source = "bigDecimal") abstract Source2 map(Target target); - protected > Enum map(String in, @TargetType Classclz ) { + protected > T map(String in, @TargetType Classclz ) { if ( clz.isAssignableFrom( SourceEnum.class )) { - return (Enum) SourceEnum.valueOf( in ); + return (T) SourceEnum.valueOf( in ); } return null; } 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 index b922dcc14f..825e318356 100644 --- 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 @@ -9,13 +9,10 @@ import java.util.Calendar; import java.util.TimeZone; -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.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; @@ -32,33 +29,20 @@ Source.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.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 { - private static final TimeZone DEFAULT_TIMEZONE = TimeZone.getDefault(); - - @BeforeClass - public static void before() { - // 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 - TimeZone.setDefault( TimeZone.getTimeZone( "UTC" ) ); - } - - @AfterClass - public static void after() { - // revert the changed default TZ - TimeZone.setDefault( DEFAULT_TIMEZONE ); - } - - @Test + @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, 02, 15, 00, 00, 00 ); + cal.set( 2018, Calendar.MARCH, 15, 00, 00, 00 ); source.setValue2( cal ); Target target = Issue1523Mapper.INSTANCE.map( source ); 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 index 92a7f99ca7..01f7ed92a9 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1541; -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; @@ -21,11 +19,10 @@ Issue1541Mapper.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1541") public class Issue1541Test { - @Test + @ProcessorTest public void testMappingWithVarArgs() { Target target = Issue1541Mapper.INSTANCE.mapWithVarArgs( "code", "1", "2" ); @@ -38,7 +35,7 @@ public void testMappingWithVarArgs() { assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); } - @Test + @ProcessorTest public void testMappingWithArray() { Target target = Issue1541Mapper.INSTANCE.mapWithArray( "code", new String[] { "1", "2" } ); @@ -52,7 +49,7 @@ public void testMappingWithArray() { assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); } - @Test + @ProcessorTest public void testMappingWithVarArgsReassignment() { Target target = Issue1541Mapper.INSTANCE.mapWithReassigningVarArgs( "code", "1", "2" ); @@ -66,7 +63,7 @@ public void testMappingWithVarArgsReassignment() { assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); } - @Test + @ProcessorTest public void testMappingWithArrayAndVarArgs() { Target target = Issue1541Mapper.INSTANCE.mapWithArrayAndVarArgs( "code", new String[] { "1", "2" }, "3", "4" ); @@ -80,7 +77,7 @@ public void testMappingWithArrayAndVarArgs() { assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); } - @Test + @ProcessorTest public void testVarArgsInAfterMappingAsArray() { Target target = Issue1541Mapper.INSTANCE.mapParametersAsArrayInAfterMapping( "code", "1", "2" ); @@ -94,7 +91,7 @@ public void testVarArgsInAfterMappingAsArray() { assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); } - @Test + @ProcessorTest public void testVarArgsInAfterMappingAsVarArgs() { Target target = Issue1541Mapper.INSTANCE.mapParametersAsVarArgsInAfterMapping( "code", "1", "2" ); @@ -108,7 +105,7 @@ public void testVarArgsInAfterMappingAsVarArgs() { assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); } - @Test + @ProcessorTest public void testVarArgsInContextWithVarArgsAfterMapping() { Target target = Issue1541Mapper.INSTANCE.mapContextWithVarArgsInAfterMappingWithVarArgs( "code", @@ -127,7 +124,7 @@ public void testVarArgsInContextWithVarArgsAfterMapping() { assertThat( target.isAfterMappingContextWithVarArgsAsArrayCalled() ).isFalse(); } - @Test + @ProcessorTest public void testVarArgsInContextWithArrayAfterMapping() { Target target = Issue1541Mapper.INSTANCE.mapContextWithVarArgsInAfterMappingWithArray( "code", 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 index c06a00bb05..2dcaf182c6 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1552; -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; @@ -20,11 +18,10 @@ Issue1552Mapper.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1552") public class Issue1552Test { - @Test + @ProcessorTest public void shouldCompile() { Target target = Issue1552Mapper.INSTANCE.twoArgsWithConstant( "first", "second" ); 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 index 9840716429..38bafc1101 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1558.java8; -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; /** * @author Sjaak Derksen @@ -20,11 +18,10 @@ Car.class, Car2.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1558") public class Issue1558Test { - @Test + @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/_1561/Issue1561Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1561/Issue1561Test.java index 4b8e5704ee..3e17e1c605 100644 --- 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 @@ -5,12 +5,10 @@ */ package org.mapstruct.ap.test.bugs._1561; -import org.junit.Rule; -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; @@ -18,7 +16,6 @@ /** * @author Sebastian Haberey */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1561") @WithClasses({ Issue1561Mapper.class, @@ -28,12 +25,12 @@ }) public class Issue1561Test { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( Issue1561Mapper.class ); - @Test + @ProcessorTest public void shouldCorrectlyUseAdder() { Source source = new Source(); 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 index a2f1040527..479d91f0e6 100644 --- 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 @@ -14,7 +14,7 @@ */ public class NestedTarget { - private List properties = new ArrayList(); + private List properties = new ArrayList<>(); public Stream getProperties() { return properties.stream(); 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 index 667f90d1ca..5de2af6ab3 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1566; -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; @@ -23,10 +21,9 @@ Target.class }) @IssueKey("1566") -@RunWith(AnnotationProcessorTestRunner.class) public class Issue1566Test { - @Test + @ProcessorTest public void genericMapperIsCorrectlyUsed() { Source source = new Source(); source.setId( "id-123" ); 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 index 079e792705..1bc7f4ca8d 100644 --- 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 @@ -10,11 +10,9 @@ import java.time.ZoneOffset; import java.util.Date; -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; @@ -26,11 +24,10 @@ Source.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1569") public class Issue1569Test { - @Test + @ProcessorTest public void shouldGenerateCorrectMapping() { Source source = new Source(); Date date = Date.from( LocalDate.of( 2018, Month.AUGUST, 5 ).atTime( 20, 30 ).toInstant( ZoneOffset.UTC ) ); 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 index 4b367a15a9..36f10eb5ad 100644 --- 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 @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.bugs._1576.java8; -import org.junit.Rule; -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; @IssueKey("1576") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses( { Issue1576Mapper.class, Source.class, Target.class }) public class Issue1576Test { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( Issue1576Mapper.class ); - @Test + @ProcessorTest public void testLocalDateTimeIsImported() { Issue1576Mapper.INSTANCE.map( new Source() ); 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 index 53355ee603..bfe79c1772 100644 --- 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 @@ -6,4 +6,14 @@ 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/Issue1590Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1590/Issue1590Test.java index 30613d1a27..0a9113797c 100644 --- 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 @@ -7,11 +7,9 @@ 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.assertj.core.api.Assertions.assertThat; @@ -24,14 +22,13 @@ Book.class, BookShelf.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1590") public class Issue1590Test { - @Test + @ProcessorTest public void shouldTestMappingLocalDates() { BookShelf source = new BookShelf(); - source.setBooks( Arrays.asList( new Book() ) ); + source.setBooks( Arrays.asList( new Book("Test") ) ); BookShelf target = BookShelfMapper.INSTANCE.map( source ); 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 index b498884b29..0c1fed523e 100644 --- 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 @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.bugs._1594; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1594") @WithClasses({ Issue1594Mapper.class }) public class Issue1594Test { - @Test + @ProcessorTest public void shouldGenerateCorrectMapping() { Issue1594Mapper.Dto dto = new Issue1594Mapper.Dto(); dto.setFullAddress( "Switzerland-Zurich" ); 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 index f92648b219..96286aa1a1 100644 --- 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 @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.test.bugs._1596; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.spi.AccessorNamingStrategy; import org.mapstruct.ap.spi.BuilderProvider; import org.mapstruct.ap.spi.ImmutablesAccessorNamingStrategy; @@ -15,10 +13,10 @@ 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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -32,7 +30,6 @@ ItemDTO.class, ImmutableItemDTO.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1596") @WithServiceImplementations( { @WithServiceImplementation( provides = BuilderProvider.class, value = Issue1569BuilderProvider.class), @@ -40,8 +37,8 @@ }) public class Issue1596Test { - @Test - public void shouldIncludeBuildeType() { + @ProcessorTest + public void shouldIncludeBuildType() { ItemDTO item = ImmutableItemDTO.builder().id( "test" ).build(); 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 index 44abfd69fa..528bc9b91b 100644 --- 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 @@ -5,18 +5,15 @@ */ package org.mapstruct.ap.test.bugs._1608; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1608") @WithClasses({ Issue1608Mapper.class, @@ -25,7 +22,7 @@ }) public class Issue1608Test { - @Test + @ProcessorTest public void shouldCorrectlyUseFluentSettersStartingWithIs() { Book book = new Book(); 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 index 03ba02d4b0..5dfe65d77e 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1648; -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; @@ -17,7 +15,6 @@ * @author Filip Hrisafov */ @IssueKey("1648") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Issue1648Mapper.class, Source.class, @@ -25,7 +22,7 @@ }) public class Issue1648Test { - @Test + @ProcessorTest public void shouldCorrectlyMarkSourceAsUsed() { Source source = new Source(); source.setSourceValue( "value" ); 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 index 860c3b21e3..ba6eeda861 100644 --- 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 @@ -15,12 +15,12 @@ public interface AMapper { AMapper INSTANCE = Mappers.getMapper( AMapper.class ); - @Mapping(source = "b.c", target = "cPrime") + @Mapping(target = "cPrime", source = "b.c") APrime toAPrime(A a, @MappingTarget APrime mappingTarget); CPrime toCPrime(C c, @MappingTarget CPrime mappingTarget); - @Mapping(source = "b.c", target = "cPrime") + @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/C.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1650/C.java index 705f2e2d69..dbbd74ec95 100644 --- 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 @@ -6,4 +6,14 @@ 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 index 24e4376f88..db504189f8 100644 --- 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 @@ -6,4 +6,14 @@ 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 index 9dd6f2dba6..33237e726c 100644 --- 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 @@ -5,16 +5,13 @@ */ package org.mapstruct.ap.test.bugs._1650; -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("1650") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ AMapper.class, A.class, @@ -25,12 +22,12 @@ }) public class Issue1650Test { - @Test + @ProcessorTest public void shouldCompile() { A a = new A(); a.setB( new B() ); - a.getB().setC( new C() ); + a.getB().setC( new C( 10 ) ); APrime aPrime = new APrime(); 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 index 838f705c20..7de37e6b43 100644 --- 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 @@ -5,18 +5,15 @@ */ package org.mapstruct.ap.test.bugs._1660; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1660") @WithClasses({ Issue1660Mapper.class, @@ -25,7 +22,7 @@ }) public class Issue1660Test { - @Test + @ProcessorTest public void shouldNotUseStaticMethods() { Source source = new Source(); source.setValue( "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 index 4690d5d5a2..169e01029a 100644 --- 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 @@ -5,21 +5,18 @@ */ package org.mapstruct.ap.test.bugs._1665; -import org.junit.Test; -import org.junit.runner.RunWith; +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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; -import java.util.ArrayList; -import java.util.List; - /** * @author Arne Seime */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1665") @WithClasses({ Issue1665Mapper.class, @@ -28,7 +25,7 @@ }) public class Issue1665Test { - @Test + @ProcessorTest public void shouldBoxIntPrimitive() { Source source = new Source(); List values = new ArrayList<>(); 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 index 812400be04..901794dea0 100644 --- 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 @@ -5,18 +5,15 @@ */ package org.mapstruct.ap.test.bugs._1681; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1681") @WithClasses({ Issue1681Mapper.class, @@ -25,7 +22,7 @@ }) public class Issue1681Test { - @Test + @ProcessorTest public void shouldCompile() { Target target = new Target( "before" ); Source source = new Source(); @@ -37,7 +34,7 @@ public void shouldCompile() { assertThat( updatedTarget.getValue() ).isEqualTo( "after" ); } - @Test + @ProcessorTest public void shouldCompileWithBuilder() { Target.Builder targetBuilder = Target.builder(); targetBuilder.builderValue( "before" ); 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 index 5bff62f4c5..eccef6df2f 100644 --- 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 @@ -5,17 +5,14 @@ */ package org.mapstruct.ap.test.bugs._1685; -import org.junit.Rule; -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; -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1685") @WithClasses({ User.class, @@ -25,12 +22,12 @@ }) public class Issue1685Test { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( UserMapper.class ); - @Test + @ProcessorTest public void testSetToNullWhenNVPMSSetToNull() { User target = new User(); @@ -58,7 +55,7 @@ public void testSetToNullWhenNVPMSSetToNull() { assertThat( target.getSettings() ).isNull(); } - @Test + @ProcessorTest public void testIgnoreWhenNVPMSIgnore() { User target = new User(); @@ -86,7 +83,7 @@ public void testIgnoreWhenNVPMSIgnore() { assertThat( target.getSettings() ).containsExactly( "test" ); } - @Test + @ProcessorTest public void testSetToDefaultWhenNVPMSSetToDefault() { User target = new User(); 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 index 38800dc757..4c838a3d55 100644 --- 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 @@ -21,11 +21,11 @@ public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper( UserMapper.class ); @Mappings({ - @Mapping(source = "email", target = "contactDataDTO.email"), - @Mapping(source = "phone", target = "contactDataDTO.phone"), - @Mapping(source = "address", target = "contactDataDTO.address"), - @Mapping(source = "preferences", target = "contactDataDTO.preferences"), - @Mapping(source = "settings", target = "contactDataDTO.settings") + @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); 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 index 77f480346a..c5af6d923f 100644 --- 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 @@ -5,26 +5,24 @@ */ package org.mapstruct.ap.test.bugs._1698; -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; -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1698") @WithClasses(Erroneous1698Mapper.class) public class Issue1698Test { - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = Erroneous1698Mapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - messageRegExp = "Can't map property.*") + 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/Issue1707Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1707/Issue1707Test.java index 5d2083312b..9798ea1bb6 100644 --- 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 @@ -5,27 +5,24 @@ */ package org.mapstruct.ap.test.bugs._1707; -import org.junit.Rule; -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; -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1707") @WithClasses({ Converter.class }) public class Issue1707Test { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( Converter.class ); - @Test + @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 index 0273457e8a..3985606d94 100644 --- 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 @@ -15,7 +15,7 @@ public interface Issue1714Mapper { Issue1714Mapper INSTANCE = Mappers.getMapper( Issue1714Mapper.class ); - @Mapping(source = "programInstance", target = "seasonNumber", qualifiedByName = "getSeasonNumber") + @Mapping(target = "seasonNumber", source = "programInstance", qualifiedByName = "getSeasonNumber") OfferEntity map(OnDemand offerStatusDTO); @Named("getTitle") 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 index 88a7848c84..4a2e42ad61 100644 --- 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 @@ -5,22 +5,19 @@ */ package org.mapstruct.ap.test.bugs._1714; -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; -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1714") @WithClasses({ Issue1714Mapper.class }) public class Issue1714Test { - @Test + @ProcessorTest public void codeShouldBeGeneratedCorrectly() { Issue1714Mapper.OnDemand source = new Issue1714Mapper.OnDemand(); 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 index e34c790322..7a8665e49f 100644 --- 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 @@ -5,16 +5,13 @@ */ package org.mapstruct.ap.test.bugs._1719; -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; import static org.assertj.core.api.Assertions.tuple; -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1719") @WithClasses({ Source.class, @@ -26,10 +23,10 @@ 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 the collection can be cleared at forehand. - * Therefore the only sensible choice is for MapStruct to create a create method for the target elements. + * 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. */ - @Test + @ProcessorTest @WithClasses(Issue1719Mapper.class) public void testShouldGiveNoErrorMessage() { Source source = new 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 index b9d28ff27b..e0755312fa 100644 --- 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 @@ -5,18 +5,15 @@ */ package org.mapstruct.ap.test.bugs._1738; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1738") @WithClasses({ Issue1738Mapper.class, @@ -25,7 +22,7 @@ }) public class Issue1738Test { - @Test + @ProcessorTest public void shouldGenerateCorrectSourceNestedMapping() { Source source = new Source(); Source.Nested nested = new Source.Nested<>(); 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/Issue1772Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1772/Issue1772Test.java index b77eb34ec0..e05938e2f9 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._1772; -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; @@ -17,7 +15,6 @@ * @author Sjaak Derksen */ @IssueKey("1772") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Issue1772Mapper.class, Source.class, @@ -25,7 +22,7 @@ }) public class Issue1772Test { - @Test + @ProcessorTest public void shouldCorrectlyMarkSourceAsUsed() { Source source = new Source(); 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/Issue1797Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1797/Issue1797Test.java index ecb531721d..bd9fcb92e0 100644 --- 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 @@ -7,11 +7,9 @@ import java.util.EnumSet; -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; @@ -19,7 +17,6 @@ * @author Filip Hrisafov */ @IssueKey("1797") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Customer.class, CustomerDto.class, @@ -27,7 +24,7 @@ }) public class Issue1797Test { - @Test + @ProcessorTest public void shouldCorrectlyMapEnumSetToEnumSet() { Customer customer = new Customer( EnumSet.of( Customer.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..80766d853a --- /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..393dc153d6 --- /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..1b8969664d --- /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..8c8a4cf01f --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +@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..a1ea466d0b --- /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 static org.assertj.core.api.Assertions.assertThat; + +import java.time.Month; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @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..a070231839 --- /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 static org.assertj.core.api.Assertions.assertThat; + +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; + +/** + * @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..4794aff940 --- /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..f6309be1c6 --- /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/Issue289Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_289/Issue289Test.java index fb9d16112a..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 @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.bugs._289; -import static org.assertj.core.api.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. @@ -27,10 +25,9 @@ SourceElement.class, TargetElement.class } ) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue289Test { - @Test + @ProcessorTest public void shouldLeaveEmptyTargetSetWhenSourceIsNullAndGetterOnlyForCreateMethod() { Source source = new Source(); @@ -41,7 +38,7 @@ public void shouldLeaveEmptyTargetSetWhenSourceIsNullAndGetterOnlyForCreateMetho assertThat( target.getCollection() ).isEmpty(); } - @Test + @ProcessorTest public void shouldLeaveEmptyTargetSetWhenSourceIsNullAndGetterOnlyForUpdateMethod() { Source source = new Source(); @@ -54,7 +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/_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/Issue306Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_306/Issue306Test.java index 712e563ca8..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 @@ -5,11 +5,9 @@ */ 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. @@ -17,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/_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..c827d32587 --- /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..e065871ea3 --- /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 org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.ArrayList; +import java.util.List; + +@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..941eee9a70 --- /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 static org.assertj.core.api.Assertions.assertThat; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.factory.Mappers; + +/** + * @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..9258914c0b --- /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/Issue373Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_373/Issue373Test.java index d860591fff..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 @@ -5,11 +5,9 @@ */ 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. @@ -17,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/_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/Issue374Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_374/Issue374Test.java index 63ed283f17..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 @@ -5,18 +5,16 @@ */ package org.mapstruct.ap.test.bugs._374; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.ArrayList; 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.assertj.core.api.Assertions.assertThat; /** * Reproducer for https://github.com/mapstruct/mapstruct/issues/306. @@ -24,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() { @@ -38,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() { @@ -81,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/_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/Issue375Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_375/Issue375Test.java index e02c56a1d8..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 @@ -5,11 +5,9 @@ */ 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. @@ -17,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 6f9b154033..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 @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.bugs._375; - /** * * @author Sjaak Derksen 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 ac9dbf93c1..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 @@ -5,18 +5,16 @@ */ package org.mapstruct.ap.test.bugs._394; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.HashMap; import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; 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 static org.assertj.core.api.Assertions.assertThat; @WithClasses( { SameNameForSourceAndTargetCarsMapper.class, @@ -26,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 8cca546200..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 @@ -21,7 +21,7 @@ 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/_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/Issue405Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/Issue405Test.java index 75f6e6952e..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 @@ -5,11 +5,9 @@ */ 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. @@ -17,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/PersonMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_405/PersonMapper.java index b9e15b1da9..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 @@ -19,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/_513/Issue513Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/Issue513Test.java index 90bbcec3c1..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 @@ -8,11 +8,11 @@ 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. @@ -34,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/TargetElement.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_513/TargetElement.java index dc794a62f3..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 @@ -5,7 +5,6 @@ */ 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 e697afc20a..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 @@ -5,7 +5,6 @@ */ 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 d999de75a1..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 @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.bugs._513; - /** * * @author Sjaak Derksen 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 e322963db7..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 @@ -5,11 +5,9 @@ */ 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. @@ -17,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/_516/Issue516Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Issue516Test.java index 5b64aca1dc..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 @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.bugs._516; -import static org.assertj.core.api.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. @@ -19,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/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_516/Target.java index 8bb32e38c0..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 @@ -26,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/Issue537Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_537/Issue537Test.java index ce5fb5dcbb..6d60aaa79e 100644 --- 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 @@ -5,18 +5,15 @@ */ package org.mapstruct.ap.test.bugs._537; -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; /** * @author Christian Bandowski */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("537") @WithClasses({ Issue537Mapper.class, @@ -27,7 +24,7 @@ }) public class Issue537Test { - @Test + @ProcessorTest public void testThatReferencedMapperWillBeUsed() { Target target = Issue537Mapper.INSTANCE.mapDto( new Source( "abc" ) ); 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 index 0773f7d271..4a6f0176d1 100644 --- 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 @@ -5,20 +5,17 @@ */ package org.mapstruct.ap.test.bugs._543; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +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.AnnotationProcessorTestRunner; import org.mapstruct.ap.testutil.runner.GeneratedSource; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("543") @WithClasses({ Issue543Mapper.class, @@ -28,10 +25,10 @@ }) public class Issue543Test { - @Rule - public GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest public void shouldCompile() { generatedSource.forMapper( Issue543Mapper.class ).containsImportFor( Source.class ); } 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 9b6672a539..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 @@ -5,20 +5,17 @@ */ package org.mapstruct.ap.test.bugs._577; -import static org.assertj.core.api.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/_581/Issue581Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_581/Issue581Test.java index cf50b344e1..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 @@ -5,21 +5,18 @@ */ package org.mapstruct.ap.test.bugs._581; -import static org.assertj.core.api.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/_590/Issue590Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_590/Issue590Test.java index 28f06b2acb..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 @@ -7,26 +7,26 @@ 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(ErroneousSourceTargetMapper.class) public class Issue590Test { - @Test + @ProcessorTest @ExpectedCompilationOutcome(value = CompilationResult.FAILED, - diagnostics = { @Diagnostic(type = ErroneousSourceTargetMapper.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/_611/Issue611Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_611/Issue611Test.java index 1a503a74b2..b77da90ad9 100644 --- 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 @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.bugs._611; -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; @@ -17,7 +15,6 @@ * @author Tillmann Gaida */ @IssueKey("611") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ SomeClass.class, SomeOtherClass.class @@ -26,7 +23,7 @@ public class Issue611Test { /** * Checks if an implementation of a nested mapper can be loaded at all. */ - @Test + @ProcessorTest public void mapperIsFound() { assertThat( SomeClass.InnerMapper.INSTANCE ).isNotNull(); } @@ -35,7 +32,7 @@ public void mapperIsFound() { * Checks if an implementation of a nested mapper can be loaded which is nested into an already * nested class. */ - @Test + @ProcessorTest public void mapperNestedInsideNestedClassIsFound() { assertThat( SomeClass.SomeInnerClass.InnerMapper.INSTANCE ).isNotNull(); } @@ -44,10 +41,10 @@ public void mapperNestedInsideNestedClassIsFound() { * Checks if it is possible to load two mapper implementations which have equal simple names * in the same package. */ - @Test + @ProcessorTest public void rightMapperIsFound() { - SomeClass.InnerMapper.Source source1 = new SomeClass.InnerMapper.Source(); - SomeOtherClass.InnerMapper.Source source2 = new SomeOtherClass.InnerMapper.Source(); + 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 ); 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 index 0d41128711..e0fb59d4d7 100644 --- 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 @@ -19,9 +19,29 @@ public interface InnerMapper { 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; + } } } @@ -33,9 +53,29 @@ public interface InnerMapper { 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 index e50187a82a..2ff2d0d831 100644 --- 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 @@ -19,9 +19,29 @@ public interface InnerMapper { 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 32fc27d56c..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 @@ -5,20 +5,17 @@ */ package org.mapstruct.ap.test.bugs._625; -import static org.assertj.core.api.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 org.mapstruct.factory.Mappers; +import static org.assertj.core.api.Assertions.assertThat; + /** * @author Andreas Gudian * */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ ObjectFactory.class, Source.class, @@ -27,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/_631/Issue631Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_631/Issue631Test.java index 5050c76ea0..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 @@ -7,22 +7,19 @@ 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, @@ -30,11 +27,11 @@ public class Issue631Test { @Diagnostic(type = ErroneousSourceTargetMapper.class, kind = Kind.ERROR, line = 22, - messageRegExp = "Can't generate mapping method for a generic type variable target."), + message = "Can't generate mapping method for a generic type variable target."), @Diagnostic(type = ErroneousSourceTargetMapper.class, kind = Kind.ERROR, line = 24, - messageRegExp = "Can't generate mapping method for a generic type variable source.") + message = "Can't generate mapping method for a generic type variable source.") } ) @WithClasses({ErroneousSourceTargetMapper.class, Base1.class, Base2.class}) 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 index a9ae5167fc..99d609408e 100644 --- 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 @@ -5,16 +5,14 @@ */ package org.mapstruct.ap.test.bugs._634; -import static org.assertj.core.api.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; + +import static org.assertj.core.api.Assertions.assertThat; /** * @author Gunnar Morling @@ -26,14 +24,13 @@ Target.class, SourceTargetMapper.class, }) -@RunWith(AnnotationProcessorTestRunner.class) public class GenericContainerTest { - @Test + @ProcessorTest @IssueKey("634") public void canMapGenericSourceTypeToGenericTargetType() { List items = Arrays.asList( new Foo( "42" ), new Foo( "84" ) ); - Source source = new Source( items ); + Source source = new Source<>( items ); Target target = SourceTargetMapper.INSTANCE.mapSourceToTarget( source ); 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 index af3d5b4245..db2f376416 100644 --- 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 @@ -5,16 +5,14 @@ */ package org.mapstruct.ap.test.bugs._775; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import org.assertj.core.api.IterableAssert; -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; /** * Verifies: @@ -26,7 +24,6 @@ * * @author Andreas Gudian */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("775") @WithClasses({ MapperWithForgedIterableMapping.class, @@ -36,7 +33,7 @@ }) public class IterableWithBoundedElementTypeTest { - @Test + @ProcessorTest public void createsForgedMethodForIterableLowerBoundInteger() { ListContainer source = new ListContainer(); @@ -44,10 +41,10 @@ public void createsForgedMethodForIterableLowerBoundInteger() { IterableContainer result = MapperWithForgedIterableMapping.INSTANCE.toContainerWithIterable( source ); ( (IterableAssert) assertThat( result.getValues() ) ) - .contains( Integer.valueOf( 42 ), Integer.valueOf( 47 ) ); + .contains( 42, 47 ); } - @Test + @ProcessorTest public void usesListIntegerMethodForIterableLowerBoundInteger() { ListContainer source = new ListContainer(); @@ -55,6 +52,6 @@ public void usesListIntegerMethodForIterableLowerBoundInteger() { IterableContainer result = MapperWithCustomListMapping.INSTANCE.toContainerWithIterable( source ); ( (IterableAssert) assertThat( result.getValues() ) ) - .contains( Integer.valueOf( 66 ), Integer.valueOf( 71 ) ); + .contains( 66, 71 ); } } 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 index 39e8a5772a..dba7dc2360 100644 --- 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 @@ -5,9 +5,9 @@ */ package org.mapstruct.ap.test.bugs._775; -import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -23,11 +23,8 @@ public abstract class MapperWithCustomListMapping { public abstract IterableContainer toContainerWithIterable(ListContainer source); protected List hexListToIntList(Collection source) { - List iterable = new ArrayList( source.size() ); - for ( String string : source ) { - iterable.add( Integer.parseInt( string, 16 ) ); - } - - return iterable; + return source.stream() + .map( string -> Integer.parseInt( string, 16 ) ) + .collect( Collectors.toList() ); } } 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 index b0d74bae26..431a999c45 100644 --- 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 @@ -30,4 +30,8 @@ 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 index ff298ad8a2..28df16ee08 100644 --- 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 @@ -28,4 +28,8 @@ 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 index 1540df73d6..4937f6c3d4 100644 --- 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 @@ -5,21 +5,18 @@ */ package org.mapstruct.ap.test.bugs._843; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Date; -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; /** * * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Commit.class, TagInfo.class, @@ -29,7 +26,7 @@ @IssueKey("843") public class Issue843Test { - @Test + @ProcessorTest public void testMapperCreation() { Commit commit = new Commit(); @@ -37,6 +34,8 @@ public void testMapperCreation() { GitlabTag gitlabTag = new GitlabTag(); gitlabTag.setCommit( commit ); + Commit.resetCallCounter(); + GitlabTag.resetCallCounter(); TagMapper.INSTANCE.gitlabTagToTagInfo( gitlabTag ); assertThat( Commit.getCallCounter() ).isEqualTo( 1 ); 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 index f52f226ef1..5ce79afa03 100644 --- 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 @@ -56,7 +56,7 @@ public void setaName(String name) { @Mapper interface MyMapper { - @Mapping(source = "name", target = "aName") + @Mapping(target = "aName", source = "name") A convert(BInterface b); @InheritInverseConfiguration 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 index 9bac413c0b..776f3f70e0 100644 --- 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 @@ -5,20 +5,17 @@ */ package org.mapstruct.ap.test.bugs._846; -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; -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("846") @WithClasses({ Mapper846.class }) public class UpdateTest { - @Test + @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 index a54a3cb3bc..2891971dfd 100644 --- 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 @@ -18,7 +18,7 @@ public interface Issue849Mapper { Issue849Mapper INSTANCE = Mappers.getMapper( Issue849Mapper.class ); - @Mapping(source = "sourceList", target = "targetList") + @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 index 313913286e..22ec5bd309 100644 --- 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 @@ -5,31 +5,27 @@ */ package org.mapstruct.ap.test.bugs._849; -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.Serializable; 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.assertj.core.api.Assertions.assertThat; /** * @author Andreas Gudian * */ @WithClasses({ Source.class, Target.class, Issue849Mapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class Issue849Test { - @Test + @ProcessorTest @IssueKey("849") public void shouldCompileWithAllImportsDeclared() { Source source = new Source(); - source.setSourceList( Arrays.asList( (Serializable) "test" ) ); + 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/Target.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_849/Target.java index b89eac46e5..8bd9e9d17b 100644 --- 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 @@ -19,7 +19,7 @@ public class Target { public List getTargetList() { if ( targetList == null ) { - targetList = new ArrayList(); + targetList = new ArrayList<>(); } return targetList; } 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 index eaeba688d0..54ee84a89c 100644 --- 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 @@ -5,23 +5,20 @@ */ package org.mapstruct.ap.test.bugs._855; -import static org.assertj.core.api.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; /** * @author Andreas Gudian * */ @WithClasses({ OrderedSource.class, OrderedTarget.class, OrderDemoMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class OrderingBug855Test { - @Test + @ProcessorTest @IssueKey("855") public void shouldApplyCorrectOrderingWithDependsOn() { OrderedSource source = new OrderedSource(); @@ -31,7 +28,7 @@ public void shouldApplyCorrectOrderingWithDependsOn() { assertThat( target.getOrder() ).containsExactly( "field2", "field0", "field1", "field3", "field4" ); } - @Test + @ProcessorTest public void shouldRetainDefaultOrderWithoutDependsOn() { OrderedSource source = new OrderedSource(); 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 index d0be73f9c6..f5196ec7b7 100644 --- 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 @@ -5,17 +5,15 @@ */ package org.mapstruct.ap.test.bugs._865; -import static org.assertj.core.api.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; /** * * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses( { ProjectDto.class, ProjectEntity.class, @@ -26,8 +24,8 @@ } ) public class Issue865Test { - @Test - public void shouldGenerateNpeCheckBeforCallingAddAllWhenInUpdateMethods() { + @ProcessorTest + public void shouldGenerateNpeCheckBeforeCallingAddAllWhenInUpdateMethods() { ProjectDto dto = new ProjectDto(); dto.setName( "myProject" ); @@ -41,7 +39,8 @@ public void shouldGenerateNpeCheckBeforCallingAddAllWhenInUpdateMethods() { assertThat( entity.getCoreUsers() ).isNull(); } - public void shouldGenerateNpeCheckBeforCallingAddAllWhenInUpdateMethodsAndTargetWithoutSetter() { + @ProcessorTest + public void shouldGenerateNpeCheckBeforeCallingAddAllWhenInUpdateMethodsAndTargetWithoutSetter() { ProjectDto dto = new ProjectDto(); dto.setName( "myProject" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java deleted file mode 100644 index 32f58384b9..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Config.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * 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; - -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/UsesConfigFromAnnotationMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java deleted file mode 100644 index ed4267cf12..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/UsesConfigFromAnnotationMapper.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * 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; - -import org.mapstruct.Mapper; - -/** - * @author Andreas Gudian - * - */ -@Mapper(componentModel = "default", config = Config.class) -public interface UsesConfigFromAnnotationMapper { - Poodle metamorph(Object essence); -} 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/DefaultsToProcessorOptionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java index c54da3f7cb..5f67b41552 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/DefaultsToProcessorOptionsMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/DefaultsToProcessorOptionsMapper.java @@ -3,7 +3,7 @@ * * 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; +package org.mapstruct.ap.test.bugs._880.spring; import org.mapstruct.Mapper; diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java similarity index 77% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java index 6ff17597c3..d2f371abed 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Issue880Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Issue880Test.java @@ -3,45 +3,45 @@ * * 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; +package org.mapstruct.ap.test.bugs._880.spring; import javax.tools.Diagnostic.Kind; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.MappingConstants; 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.AnnotationProcessorTestRunner; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.stereotype.Component; /** * @author Andreas Gudian */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ UsesConfigFromAnnotationMapper.class, DefaultsToProcessorOptionsMapper.class, Poodle.class, Config.class }) @ProcessorOptions({ - @ProcessorOption(name = "mapstruct.defaultComponentModel", value = "spring"), + @ProcessorOption(name = "mapstruct.defaultComponentModel", value = MappingConstants.ComponentModel.SPRING), @ProcessorOption(name = "mapstruct.unmappedTargetPolicy", value = "ignore") }) +@WithSpring public class Issue880Test { - @Rule - public GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest @ExpectedCompilationOutcome( value = CompilationResult.SUCCEEDED, diagnostics = @Diagnostic(kind = Kind.WARNING, - type = UsesConfigFromAnnotationMapper.class, line = 16, - messageRegExp = "Unmapped target property: \"core\"\\.")) + type = UsesConfigFromAnnotationMapper.class, line = 17, + message = "Unmapped target property: \"core\".")) public void compilationSucceedsAndAppliesCorrectComponentModel() { generatedSource.forMapper( UsesConfigFromAnnotationMapper.class ).containsNoImportFor( Component.class ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java rename to processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java index e822db1cfe..71166f6d4c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/Poodle.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_880/spring/Poodle.java @@ -3,7 +3,7 @@ * * 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; +package org.mapstruct.ap.test.bugs._880.spring; /** * @author Andreas Gudian 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 index ebc654bf16..fbb9fcd6c9 100644 --- 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 @@ -18,6 +18,6 @@ public interface BuggyMapper { BuggyMapper INSTANCE = Mappers.getMapper( BuggyMapper.class ); - @Mapping(source = "nested.propInt", target = "propLong") + @Mapping(target = "propLong", source = "nested.propInt") Dest convert(Src src); } 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 index a520a055d4..5fae478f2e 100644 --- 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 @@ -5,19 +5,16 @@ */ package org.mapstruct.ap.test.bugs._891; -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; /** * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({BuggyMapper.class, Dest.class, Src.class, SrcNested.class}) public class Issue891Test { - @Test + @ProcessorTest public void shouldNotThrowNPE() { BuggyMapper.INSTANCE.convert( new Src() ); 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 index 6291a56233..0064a96338 100644 --- 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 @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.bugs._892; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; 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.ap.testutil.runner.AnnotationProcessorTestRunner; 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 */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Issue892Mapper.class }) public class Issue892Test { - @Test + @ProcessorTest public void compiles() { Source src = new Source(); src.setType( 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 index 9de76b43d8..320c7d1306 100644 --- 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 @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.bugs._895; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.bugs._895.MultiArrayMapper.WithArrayOfByteArray; import org.mapstruct.ap.test.bugs._895.MultiArrayMapper.WithListOfByteArray; +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.assertj.core.api.Assertions.assertThat; + /** * Verifies that forged iterable mapping methods for multi-dimensional arrays are generated properly. * * @author Andreas Gudian */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses(MultiArrayMapper.class) public class Issue895Test { - @Test + @ProcessorTest public void properlyMapsMultiDimensionalArrays() { WithArrayOfByteArray arrayOfByteArray = new WithArrayOfByteArray(); arrayOfByteArray.setBytes( new byte[][] { new byte[] { 0, 1 }, new byte[] { 1, 2 } } ); @@ -32,6 +29,6 @@ public void properlyMapsMultiDimensionalArrays() { assertThat( listOfByteArray.getBytes() ).containsExactly( new byte[] { 0, 1 }, new byte[] { 1, 2 } ); arrayOfByteArray = Mappers.getMapper( MultiArrayMapper.class ).convert( listOfByteArray ); - assertThat( arrayOfByteArray.getBytes() ).containsExactly( new byte[] { 0, 1 }, new byte[] { 1, 2 } ); + assertThat( arrayOfByteArray.getBytes() ).isDeepEqualTo( new byte[][] { { 0, 1 }, { 1, 2 } } ); } } 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 index da74677ee2..20710488f7 100644 --- 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 @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.bugs._909; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; 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.ap.testutil.runner.AnnotationProcessorTestRunner; 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 */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses(ValuesMapper.class) public class Issue909Test { - @Test + @ProcessorTest public void properlyCreatesMapperWithValuesAsParameterName() { ValuesHolder valuesHolder = new ValuesHolder(); valuesHolder.setValues( "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 index 2eb2e85030..500adf3066 100644 --- 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 @@ -14,8 +14,8 @@ * @author Sjaak Derksen */ public class Domain { - static final Set DEFAULT_STRINGS = new HashSet(); - static final Set DEFAULT_LONGS = new HashSet(); + static final Set DEFAULT_STRINGS = new HashSet<>(); + static final Set DEFAULT_LONGS = new HashSet<>(); private Set strings = DEFAULT_STRINGS; private Set longs = DEFAULT_LONGS; 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 index df821ca182..a808d78737 100644 --- 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 @@ -16,11 +16,11 @@ */ 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(); + 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; 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 index dd152beccc..507180bc07 100644 --- 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 @@ -16,7 +16,7 @@ public class Dto { private List strings; - private List stringsInitialized = new ArrayList( Arrays.asList( "5" ) ); + private List stringsInitialized = new ArrayList<>( Arrays.asList( "5" ) ); private List stringsWithDefault; public List getStrings() { 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 index 3d55e1ec21..27c262da12 100644 --- 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 @@ -16,7 +16,7 @@ public class DtoWithPresenceCheck { private List strings; - private List stringsInitialized = new ArrayList( Arrays.asList( "5" ) ); + private List stringsInitialized = new ArrayList<>( Arrays.asList( "5" ) ); private List stringsWithDefault; public boolean hasStrings() { 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 index c9c3182edc..4e10a4895f 100644 --- 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 @@ -5,12 +5,11 @@ */ package org.mapstruct.ap.test.bugs._913; -import static org.assertj.core.api.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; /** * All these test cases test the possible combinations in the GetterMapperForCollections. @@ -19,7 +18,6 @@ * * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ DomainWithoutSetter.class, Dto.class, @@ -36,7 +34,7 @@ public class Issue913GetterMapperForCollectionsTest { * 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. */ - @Test + @ProcessorTest public void shouldReturnNullForNvmsReturnNullForCreate() { Dto dto = new Dto(); @@ -52,7 +50,7 @@ public void shouldReturnNullForNvmsReturnNullForCreate() { * 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. */ - @Test + @ProcessorTest public void shouldReturnNullForNvmsReturnNullForUpdate() { Dto dto = new Dto(); @@ -70,7 +68,7 @@ public void shouldReturnNullForNvmsReturnNullForUpdate() { * 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. */ - @Test + @ProcessorTest public void shouldReturnNullForNvmsReturnNullForUpdateWithReturn() { Dto dto = new Dto(); @@ -92,7 +90,7 @@ public void shouldReturnNullForNvmsReturnNullForUpdateWithReturn() { * * However, for plain mappings (strings to strings) the result will be null. */ - @Test + @ProcessorTest public void shouldReturnDefaultForNvmsReturnDefaultForCreate() { Dto dto = new Dto(); @@ -110,7 +108,7 @@ public void shouldReturnDefaultForNvmsReturnDefaultForCreate() { * * However, for plain mappings (strings to strings) the result will be null. */ - @Test + @ProcessorTest public void shouldReturnDefaultForNvmsReturnDefaultForUpdate() { Dto dto = new Dto(); @@ -131,7 +129,7 @@ public void shouldReturnDefaultForNvmsReturnDefaultForUpdate() { * However, for plain mappings (strings to strings) the result will be null. * */ - @Test + @ProcessorTest public void shouldReturnDefaultForNvmsReturnDefaultForUpdateWithReturn() { Dto dto = new Dto(); @@ -151,7 +149,7 @@ public void shouldReturnDefaultForNvmsReturnDefaultForUpdateWithReturn() { * Test create method ICW presence checker * */ - @Test + @ProcessorTest public void shouldReturnNullForCreateWithPresenceChecker() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); @@ -166,7 +164,7 @@ public void shouldReturnNullForCreateWithPresenceChecker() { * Test update method ICW presence checker * */ - @Test + @ProcessorTest public void shouldReturnNullForUpdateWithPresenceChecker() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); @@ -182,7 +180,7 @@ public void shouldReturnNullForUpdateWithPresenceChecker() { * Test update with return method ICW presence checker * */ - @Test + @ProcessorTest public void shouldReturnNullForUpdateWithReturnWithPresenceChecker() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); @@ -198,8 +196,6 @@ public void shouldReturnNullForUpdateWithReturnWithPresenceChecker() { assertThat( domain2.getLongs() ).isEmpty(); } - - /** * These assert check if non-null and default mapping is working as expected. * 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 index 29ad1e5bfa..a549011446 100644 --- 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 @@ -5,19 +5,17 @@ */ package org.mapstruct.ap.test.bugs._913; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.HashSet; import java.util.Set; -import org.junit.Rule; -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; + /** * All these test cases test the possible combinations in the SetterMapperForCollections. * @@ -25,7 +23,6 @@ * * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Domain.class, Dto.class, @@ -38,8 +35,8 @@ @IssueKey( "913" ) public class Issue913SetterMapperForCollectionsTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( DomainDtoWithNvmsNullMapper.class, DomainDtoWithNvmsDefaultMapper.class, DomainDtoWithPresenceCheckMapper.class, @@ -55,7 +52,7 @@ public class Issue913SetterMapperForCollectionsTest { * 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} */ - @Test + @ProcessorTest public void shouldReturnNullForNvmsReturnNullForCreate() { Dto dto = new Dto(); @@ -71,13 +68,13 @@ public void shouldReturnNullForNvmsReturnNullForCreate() { * 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. */ - @Test + @ProcessorTest public void shouldReturnNullForNvmsReturnNullForUpdate() { Dto dto = new Dto(); Domain domain = new Domain(); - domain.setLongs( new HashSet() ); - domain.setStrings( new HashSet() ); + domain.setLongs( new HashSet<>() ); + domain.setStrings( new HashSet<>() ); DomainDtoWithNvmsNullMapper.INSTANCE.update( dto, domain ); doControlAsserts( domain ); @@ -94,14 +91,14 @@ public void shouldReturnNullForNvmsReturnNullForUpdate() { * target (stringsInitialized is Not Null) and source (stringInitialized is Null) target should * be explicitely set to null */ - @Test + @ProcessorTest public void shouldReturnNullForNvmsReturnNullForUpdateWithNonNullTargetAndNullSource() { Dto dto = new Dto(); dto.setStringsInitialized( null ); Domain domain = new Domain(); - domain.setLongs( new HashSet() ); - domain.setStrings( new HashSet() ); + domain.setLongs( new HashSet<>() ); + domain.setStrings( new HashSet<>() ); DomainDtoWithNvmsNullMapper.INSTANCE.update( dto, domain ); assertThat( domain.getStringsInitialized() ).isNull(); @@ -117,13 +114,13 @@ public void shouldReturnNullForNvmsReturnNullForUpdateWithNonNullTargetAndNullSo * 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. */ - @Test + @ProcessorTest public void shouldReturnNullForNvmsReturnNullForUpdateWithReturn() { Dto dto = new Dto(); Domain domain1 = new Domain(); - domain1.setLongs( new HashSet() ); - domain1.setStrings( new HashSet() ); + domain1.setLongs( new HashSet<>() ); + domain1.setStrings( new HashSet<>() ); Domain domain2 = DomainDtoWithNvmsNullMapper.INSTANCE.updateWithReturn( dto, domain1 ); doControlAsserts( domain1, domain2 ); @@ -140,7 +137,7 @@ public void shouldReturnNullForNvmsReturnNullForUpdateWithReturn() { * * However, for plain mappings (strings to strings) the result will also be an empty collection. */ - @Test + @ProcessorTest public void shouldReturnDefaultForNvmsReturnDefaultForCreate() { Dto dto = new Dto(); @@ -158,15 +155,15 @@ public void shouldReturnDefaultForNvmsReturnDefaultForCreate() { * * However, for plain mappings (strings to strings) the result will also be an empty collection. */ - @Test + @ProcessorTest public void shouldReturnDefaultForNvmsReturnDefaultForUpdate() { Dto dto = new Dto(); Domain domain = new Domain(); - Set longIn = new HashSet(); + Set longIn = new HashSet<>(); longIn.add( 10L ); domain.setLongs( longIn ); - domain.setStrings( new HashSet() ); + domain.setStrings( new HashSet<>() ); DomainDtoWithNvmsDefaultMapper.INSTANCE.update( dto, domain ); doControlAsserts( domain ); @@ -184,15 +181,15 @@ public void shouldReturnDefaultForNvmsReturnDefaultForUpdate() { * However, for plain mappings (strings to strings) the result will also be an empty collection. * */ - @Test + @ProcessorTest public void shouldReturnDefaultForNvmsReturnDefaultForUpdateWithReturn() { Dto dto = new Dto(); Domain domain1 = new Domain(); - Set longIn = new HashSet(); + Set longIn = new HashSet<>(); longIn.add( 10L ); domain1.setLongs( longIn ); - domain1.setStrings( new HashSet() ); + domain1.setStrings( new HashSet<>() ); domain1.getStrings().add( "30" ); Domain domain2 = DomainDtoWithNvmsDefaultMapper.INSTANCE.updateWithReturn( dto, domain1 ); @@ -211,7 +208,7 @@ public void shouldReturnDefaultForNvmsReturnDefaultForUpdateWithReturn() { * Test create method ICW presence checker. The presence checker is responsible for the null check. * */ - @Test + @ProcessorTest public void shouldReturnNullForCreateWithPresenceChecker() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); @@ -229,14 +226,14 @@ public void shouldReturnNullForCreateWithPresenceChecker() { * */ @IssueKey( "#954") - @Test + @ProcessorTest public void shouldReturnNullForUpdateWithPresenceChecker() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); Domain domain = new Domain(); - domain.setLongs( new HashSet() ); + domain.setLongs( new HashSet<>() ); domain.getLongs().add( 10L ); - domain.setStrings( new HashSet() ); + domain.setStrings( new HashSet<>() ); domain.getStrings().add( "30" ); DomainDtoWithPresenceCheckMapper.INSTANCE.update( dto, domain ); @@ -252,14 +249,14 @@ public void shouldReturnNullForUpdateWithPresenceChecker() { * */ @IssueKey( "#954") - @Test + @ProcessorTest public void shouldReturnNullForUpdateWithReturnWithPresenceChecker() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); Domain domain1 = new Domain(); - domain1.setLongs( new HashSet() ); + domain1.setLongs( new HashSet<>() ); domain1.getLongs().add( 10L ); - domain1.setStrings( new HashSet() ); + domain1.setStrings( new HashSet<>() ); domain1.getStrings().add( "30" ); Domain domain2 = DomainDtoWithPresenceCheckMapper.INSTANCE.updateWithReturn( dto, domain1 ); @@ -277,7 +274,7 @@ public void shouldReturnNullForUpdateWithReturnWithPresenceChecker() { * */ @IssueKey( "#954") - @Test + @ProcessorTest public void shouldReturnNullForCreateWithNcvsAlways() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); @@ -295,14 +292,14 @@ public void shouldReturnNullForCreateWithNcvsAlways() { * */ @IssueKey( "#954") - @Test + @ProcessorTest public void shouldReturnNullForUpdateWithNcvsAlways() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); Domain domain = new Domain(); - domain.setLongs( new HashSet() ); + domain.setLongs( new HashSet<>() ); domain.getLongs().add( 10L ); - domain.setStrings( new HashSet() ); + domain.setStrings( new HashSet<>() ); domain.getStrings().add( "30" ); DomainDtoWithNcvsAlwaysMapper.INSTANCE.update( dto, domain ); @@ -318,14 +315,14 @@ public void shouldReturnNullForUpdateWithNcvsAlways() { * */ @IssueKey( "#954") - @Test + @ProcessorTest public void shouldReturnNullForUpdateWithReturnWithNcvsAlways() { DtoWithPresenceCheck dto = new DtoWithPresenceCheck(); Domain domain1 = new Domain(); - domain1.setLongs( new HashSet() ); + domain1.setLongs( new HashSet<>() ); domain1.getLongs().add( 10L ); - domain1.setStrings( new HashSet() ); + domain1.setStrings( new HashSet<>() ); domain1.getStrings().add( "30" ); Domain domain2 = DomainDtoWithNcvsAlwaysMapper.INSTANCE.updateWithReturn( dto, domain1 ); 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 index 8fc0e0cbcf..25c2baf89d 100644 --- 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 @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.bugs._931; -import static org.assertj.core.api.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; /** * Verifies that source.nested == null, leads to target.id == null @@ -19,10 +17,9 @@ * @author Sjaak Derksen */ @IssueKey( "931" ) -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses( { Source.class, Nested.class, Target.class, SourceTargetMapper.class } ) public class Issue931Test { - @Test + @ProcessorTest public void shouldMapNestedNullToNull() { Source source = new Source(); 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 index 8178668900..4e03de5373 100644 --- 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 @@ -5,24 +5,21 @@ */ package org.mapstruct.ap.test.bugs._955; -import org.junit.Test; -import org.junit.runner.RunWith; 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; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * * @author Sjaak Derksen */ @IssueKey( "955" ) -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses( { CarMapper.class, CustomMapper.class, Car.class, SuperCar.class, Person.class } ) public class Issue955Test { - @Test + @ProcessorTest public void shouldCompile() { } } 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 index 236f1641c9..fe178f815b 100644 --- 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 @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.bugs._971; -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; /** * @author Filip Hrisafov * */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("971") public class Issue971Test { - @Test + @ProcessorTest @WithClasses({ CollectionSource.class, CollectionTarget.class, Issue971CollectionMapper.class }) public void shouldCompileForCollections() { } - @Test + @ProcessorTest @WithClasses({ MapSource.class, MapTarget.class, Issue971MapMapper.class }) public void shouldCompileForMaps() { } } 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 index 8ff3bf9fad..47a534d20a 100644 --- 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 @@ -5,10 +5,8 @@ */ package org.mapstruct.ap.test.builder.abstractBuilder; -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; @@ -23,7 +21,6 @@ ProductDto.class, ProductMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class AbstractBuilderTest { /** @@ -35,7 +32,7 @@ public class AbstractBuilderTest { * - all values are mapped * - all values are set properly */ - @Test + @ProcessorTest public void testThatAbstractBuilderMapsAllProperties() { ImmutableProduct product = ProductMapper.INSTANCE.fromMutable( new ProductDto( "router", 31 ) ); @@ -43,7 +40,7 @@ public void testThatAbstractBuilderMapsAllProperties() { assertThat( product.getName() ).isEqualTo( "router" ); } - @Test + @ProcessorTest public void testThatAbstractBuilderReverseMapsAllProperties() { ProductDto product = ProductMapper.INSTANCE.fromImmutable( ImmutableProduct.builder() .price( 31000 ) 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 index 8ff58c5af3..2b77353175 100644 --- 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 @@ -5,10 +5,8 @@ */ package org.mapstruct.ap.test.builder.abstractGenericTarget; -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; @@ -26,10 +24,9 @@ ParentSource.class, ParentMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class AbstractGenericTargetBuilderTest { - @Test + @ProcessorTest public void testAbstractTargetMapper() { ParentSource parent = new ParentSource(); parent.setCount( 4 ); 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 index 57641a6dc1..5ec38b2d0a 100644 --- 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 @@ -5,17 +5,14 @@ */ package org.mapstruct.ap.test.builder.factory; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ BuilderFactoryMapper.class, BuilderImplicitFactoryMapper.class, @@ -24,7 +21,7 @@ }) public class BuilderFactoryMapperTest { - @Test + @ProcessorTest public void shouldUseBuilderFactory() { Person person = BuilderFactoryMapper.INSTANCE.map( new PersonDto( "Filip" ) ); @@ -32,7 +29,7 @@ public void shouldUseBuilderFactory() { assertThat( person.getSource() ).isEqualTo( "Factory with @ObjectFactory" ); } - @Test + @ProcessorTest public void shouldUseImplicitBuilderFactory() { Person person = BuilderImplicitFactoryMapper.INSTANCE.map( new PersonDto( "Filip" ) ); 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 index 58848b0b8d..9d26b94327 100644 --- 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 @@ -20,6 +20,7 @@ public interface BuilderIgnoringMapper { BuilderIgnoringMapper INSTANCE = Mappers.getMapper( BuilderIgnoringMapper.class ); @InheritConfiguration(name = "mapBase") + @Mapping( target = "lastName" ) Person mapWithIgnoringBase(PersonDto source); @BeanMapping(ignoreByDefault = true) 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 index d179a17ff7..5be9d50ff6 100644 --- 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 @@ -5,18 +5,15 @@ */ package org.mapstruct.ap.test.builder.ignore; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1452") @WithClasses({ BaseDto.class, @@ -28,7 +25,8 @@ }) public class BuilderIgnoringTest { - @Test + @ProcessorTest + @IssueKey( "1933" ) public void shouldIgnoreBase() { PersonDto source = new PersonDto(); source.setId( 100L ); @@ -38,11 +36,11 @@ public void shouldIgnoreBase() { Person target = BuilderIgnoringMapper.INSTANCE.mapWithIgnoringBase( source ); assertThat( target.getId() ).isNull(); - assertThat( target.getName() ).isEqualTo( "John" ); + assertThat( target.getName() ).isNull(); assertThat( target.getLastName() ).isEqualTo( "Doe" ); } - @Test + @ProcessorTest public void shouldMapOnlyExplicit() { PersonDto source = new PersonDto(); source.setId( 100L ); @@ -56,7 +54,7 @@ public void shouldMapOnlyExplicit() { assertThat( target.getLastName() ).isNull(); } - @Test + @ProcessorTest public void shouldMapAll() { PersonDto source = new PersonDto(); source.setId( 100L ); 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 index 706a08eea1..3d5fb7e533 100644 --- 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 @@ -7,18 +7,15 @@ 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.assertj.core.api.Assertions.assertThat; /** * @author Filip Hrisafov */ -@RunWith( AnnotationProcessorTestRunner.class ) @IssueKey( "1433" ) @WithClasses( { Item.class, @@ -30,7 +27,7 @@ } ) public class BuilderLifecycleCallbacksTest { - @Test + @ProcessorTest public void lifecycleMethodsShouldBeInvoked() { OrderDto source = new OrderDto(); source.setCreator( "Filip" ); @@ -46,12 +43,16 @@ public void lifecycleMethodsShouldBeInvoked() { assertThat( context.getInvokedMethods() ) .contains( "beforeWithoutParameters", + "beforeWithTargetType", "beforeWithBuilderTargetType", "beforeWithBuilderTarget", "afterWithoutParameters", "afterWithBuilderTargetType", "afterWithBuilderTarget", - "afterWithBuilderTargetReturningTarget" + "afterWithBuilderTargetReturningTarget", + "afterWithTargetType", + "afterWithTarget", + "afterWithTargetReturningTarget" ); } } 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 index 96b9b30db6..2065bad478 100644 --- 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 @@ -18,7 +18,7 @@ */ public class MappingContext { - private final List invokedMethods = new ArrayList(); + private final List invokedMethods = new ArrayList<>(); @BeforeMapping public void beforeWithoutParameters() { @@ -74,7 +74,15 @@ public void afterWithBuilderTarget(OrderDto source, @MappingTarget Order.Builder public Order afterWithBuilderTargetReturningTarget(@MappingTarget Order.Builder orderBuilder) { invokedMethods.add( "afterWithBuilderTargetReturningTarget" ); - return orderBuilder.create(); + // 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() { 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 index 5342a5e444..54316bf774 100644 --- 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 @@ -17,7 +17,7 @@ public class Order { private final String creator; public Order(Builder builder) { - this.items = new ArrayList( builder.items ); + this.items = new ArrayList<>( builder.items ); this.creator = builder.creator; } @@ -34,7 +34,7 @@ public static Builder builder() { } public static class Builder { - private List items = new ArrayList(); + private List items = new ArrayList<>(); private String creator; public Builder items(List items) { 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 index e44912ff99..5d0ff919ec 100644 --- 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 @@ -5,11 +5,13 @@ */ package org.mapstruct.ap.test.builder.mappingTarget.simple; -import org.junit.Rule; -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.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; @@ -20,13 +22,12 @@ SimpleImmutableTarget.class, SimpleBuilderMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class BuilderInfoTargetTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest public void testSimpleImmutableBuilderHappyPath() { SimpleMutableSource source = new SimpleMutableSource(); source.setAge( 3 ); @@ -39,7 +40,19 @@ public void testSimpleImmutableBuilderHappyPath() { assertThat( targetObject.getName() ).isEqualTo( "Bob" ); } - @Test + @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 ); @@ -50,7 +63,7 @@ public void testMutableTargetWithBuilder() { assertThat( target.getSource() ).isEqualTo( "Builder" ); } - @Test + @ProcessorTest public void testUpdateMutableWithBuilder() { SimpleMutableSource source = new SimpleMutableSource(); source.setAge( 20 ); @@ -69,7 +82,18 @@ public void testUpdateMutableWithBuilder() { assertThat( target.getSource() ).isEqualTo( "Empty constructor" ); } - @Test + @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(); @@ -80,7 +104,7 @@ public void updatingTargetWithNoSettersShouldNotFail() { .build(); assertThat( target.getAge() ).isEqualTo( 20 ); - SimpleBuilderMapper.INSTANCE.toImmutable( source, target ); + SimpleImmutableUpdateMapper.INSTANCE.toImmutable( source, target ); assertThat( target.getAge() ).isEqualTo( 20 ); } } 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 index 9f7187c634..6185daee97 100644 --- 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 @@ -21,9 +21,6 @@ public interface SimpleBuilderMapper { @Mapping(target = "builder.name", source = "source.fullName") void updateImmutable(SimpleMutableSource source, @MappingTarget SimpleImmutableTarget.Builder builder); - // 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); - @Mapping(target = "name", source = "fullName") SimpleImmutableTarget toImmutable(SimpleMutableSource source); 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/multiple/MultipleBuilderMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/multiple/MultipleBuilderMapperTest.java index 2d0fe0f78a..825794d6d7 100644 --- 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 @@ -5,23 +5,20 @@ */ package org.mapstruct.ap.test.builder.multiple; -import org.junit.Test; -import org.junit.runner.RunWith; 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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1479") @WithClasses({ Process.class, @@ -40,26 +37,20 @@ public class MultipleBuilderMapperTest { type = ErroneousMoreThanOneBuildMethodMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 19, - messageRegExp = "No build method \"build\" found in \".*\\.multiple\\.build\\.Process\\.Builder\" " + - "for \".*\\.multiple\\.build\\.Process\"\\. " + - "Found methods: " + - "\".*wrongCreate\\(\\) ?, " + - ".*create\\(\\) ?\"\\. " + - "Consider to add @Builder in order to select the correct build method." + 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, - messageRegExp = "No build method \"missingBuild\" found " + - "in \".*\\.multiple\\.build\\.Process\\.Builder\" " + - "for \".*\\.multiple\\.build\\.Process\"\\. " + - "Found methods: " + - "\".*wrongCreate\\(\\) ?, " + - ".*create\\(\\) ?\"\\." + 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()\"." ) }) - @Test + @ProcessorTest public void moreThanOneBuildMethod() { } @@ -72,22 +63,20 @@ public void moreThanOneBuildMethod() { type = ErroneousMoreThanOneBuildMethodWithMapperDefinedMappingMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 18, - messageRegExp = - "No build method \"mapperBuild\" found in \".*\\.multiple\\.build\\.Process\\.Builder\" " + - "for \".*\\.multiple\\.build\\.Process\"\\. " + - "Found methods: " + - "\".*wrongCreate\\(\\) ?, " + - ".*create\\(\\) ?\"\\." + 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()\"." ) }) - @Test + @ProcessorTest public void moreThanOneBuildMethodDefinedOnMapper() { } @WithClasses({ BuilderDefinedMapper.class }) - @Test + @ProcessorTest public void builderMappingDefined() { Process map = BuilderDefinedMapper.INSTANCE.map( new Source( "map" ) ); Process wrongMap = BuilderDefinedMapper.INSTANCE.wrongMap( new Source( "wrongMap" ) ); @@ -100,7 +89,7 @@ public void builderMappingDefined() { BuilderMapperConfig.class, BuilderConfigDefinedMapper.class }) - @Test + @ProcessorTest public void builderMappingMapperConfigDefined() { Process map = BuilderConfigDefinedMapper.INSTANCE.map( new Source( "map" ) ); Process wrongMap = BuilderConfigDefinedMapper.INSTANCE.wrongMap( new Source( "wrongMap" ) ); @@ -113,30 +102,17 @@ public void builderMappingMapperConfigDefined() { TooManyBuilderCreationMethodsMapper.class }) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, - // We have 2 diagnostics, as we don't do caching of the types, so a type is processed multiple types diagnostics = { @Diagnostic( type = Case.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 11, - messageRegExp = "More than one builder creation method for \".*\\.multiple\\.builder.Case\"\\. " + - "Found methods: " + - "\".*wrongBuilder\\(\\) ?, " + - ".*builder\\(\\) ?\"\\. " + - "Builder will not be used\\. Consider implementing a custom BuilderProvider SPI\\." - ), - @Diagnostic( - type = Case.class, - kind = javax.tools.Diagnostic.Kind.WARNING, - line = 11, - messageRegExp = "More than one builder creation method for \".*\\.multiple\\.builder.Case\"\\. " + - "Found methods: " + - "\".*wrongBuilder\\(\\) ?, " + - ".*builder\\(\\) ?\"\\. " + - "Builder will not be used\\. Consider implementing a custom BuilderProvider SPI\\." + 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." ) }) - @Test + @ProcessorTest public void tooManyBuilderCreationMethods() { Case caseTarget = TooManyBuilderCreationMethodsMapper.INSTANCE.map( new Source( "test" ) ); @@ -149,7 +125,7 @@ public void tooManyBuilderCreationMethods() { @WithClasses( { DefaultBuildMethodMapper.class } ) - @Test + @ProcessorTest public void defaultBuildMethod() { Task task = DefaultBuildMethodMapper.INSTANCE.map( new Source( "test" ) ); 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 index 45073dd906..94233a373e 100644 --- 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 @@ -5,10 +5,8 @@ */ package org.mapstruct.ap.test.builder.nestedprop.expanding; -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; @@ -21,10 +19,9 @@ ImmutableArticle.class, ExpandingMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class BuilderNestedPropertyTest { - @Test + @ProcessorTest public void testNestedImmutablePropertyMapper() { FlattenedStock stock = new FlattenedStock( "Sock", "Tie", 33 ); ImmutableExpandedStock expandedTarget = ExpandingMapper.INSTANCE.writeToNestedProperty( stock ); 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 index 70cbbb7f84..f7cce1a03f 100644 --- 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 @@ -5,7 +5,7 @@ */ package org.mapstruct.ap.test.builder.nestedprop.expanding; -import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Objects.requireNonNull; public class FlattenedStock { private String article1; @@ -16,8 +16,8 @@ public FlattenedStock() { } public FlattenedStock(String article1, String article2, int count) { - this.article1 = checkNotNull( article1 ); - this.article2 = checkNotNull( article2 ); + this.article1 = requireNonNull( article1 ); + this.article2 = requireNonNull( article2 ); this.count = count; } 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 index a25e224472..861bf810bf 100644 --- 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 @@ -5,10 +5,8 @@ */ package org.mapstruct.ap.test.builder.nestedprop.flattening; -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; @@ -21,10 +19,9 @@ Article.class, FlatteningMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class BuilderNestedPropertyTest { - @Test + @ProcessorTest public void testNestedImmutablePropertyMapper() { Stock stock = new Stock(); 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 index 6334be7ff0..e90babfdd8 100644 --- 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 @@ -5,20 +5,17 @@ */ package org.mapstruct.ap.test.builder.noop; -import org.junit.Test; -import org.junit.runner.RunWith; 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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; /** * @author Filip Hrisafov */ -@RunWith( AnnotationProcessorTestRunner.class ) @IssueKey( "1418" ) @WithServiceImplementation(NoOpBuilderProvider.class) @WithClasses( { @@ -28,7 +25,7 @@ } ) public class NoOpBuilderProviderTest { - @Test + @ProcessorTest public void shouldNotUseBuilder() { Person person = PersonMapper.INSTANCE.map( new PersonDto( "Filip" ) ); 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/ParentChildBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/parentchild/ParentChildBuilderTest.java index f1a3b4d617..562770a2bb 100644 --- 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 @@ -8,10 +8,8 @@ import java.util.ArrayList; import org.assertj.core.api.Condition; -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; @@ -22,14 +20,13 @@ ImmutableParent.class, ParentChildMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ParentChildBuilderTest { - @Test + @ProcessorTest public void testParentChildBuilderMapper() { final MutableParent parent = new MutableParent(); parent.setCount( 4 ); - parent.setChildren( new ArrayList() ); + parent.setChildren( new ArrayList<>() ); parent.getChildren().add( new MutableChild( "Phineas" ) ); parent.getChildren().add( new MutableChild( "Ferb" ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java deleted file mode 100644 index 305ae77e80..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/ErroneousSimpleBuilderMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright MapStruct 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 org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.ReportingPolicy; - -@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR) -public interface ErroneousSimpleBuilderMapper { - - @Mappings({ - @Mapping(target = "address", ignore = true ), - @Mapping(target = "job", ignore = true ), - @Mapping(target = "city", ignore = true ) - }) - SimpleImmutablePerson toImmutable(SimpleMutablePerson source); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java deleted file mode 100644 index 0b0e961b30..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleBuilderMapper.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright MapStruct 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 org.mapstruct.CollectionMappingStrategy; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -@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\")") - }) - SimpleImmutablePerson toImmutable(SimpleMutablePerson source); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java deleted file mode 100644 index a7e579b290..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutableBuilderTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright MapStruct 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.Arrays; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -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; -import org.mapstruct.factory.Mappers; - -import static org.assertj.core.api.Assertions.assertThat; - -@WithClasses({ - SimpleMutablePerson.class, - SimpleImmutablePerson.class -}) -@RunWith(AnnotationProcessorTestRunner.class) -public class SimpleImmutableBuilderTest { - - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); - - @Test - @WithClasses({ SimpleBuilderMapper.class }) - public void testSimpleImmutableBuilderHappyPath() { - 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" ); - - SimpleImmutablePerson 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" ); - } - - @Test - @WithClasses({ ErroneousSimpleBuilderMapper.class }) - @ExpectedCompilationOutcome(value = CompilationResult.FAILED, - diagnostics = @Diagnostic( - kind = javax.tools.Diagnostic.Kind.ERROR, - type = ErroneousSimpleBuilderMapper.class, - line = 21, - messageRegExp = "Unmapped target property: \"name\"\\.")) - public void testSimpleImmutableBuilderMissingPropertyFailsToCompile() { - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java b/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java deleted file mode 100644 index daa3dfad0b..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/builder/simple/SimpleImmutablePerson.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright MapStruct 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.ArrayList; -import java.util.List; - -public class SimpleImmutablePerson { - private final String name; - private final int age; - private final String job; - private final String city; - private final String address; - private final List children; - - SimpleImmutablePerson(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(); - - public Builder age(int age) { - this.age = age; - return this; - } - - public SimpleImmutablePerson build() { - return new SimpleImmutablePerson( 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/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 d5e2de5b64..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 @@ -17,24 +17,20 @@ 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.FixMethodOrder; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.MethodSorters; +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.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; @@ -51,6 +47,8 @@ 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; @@ -62,8 +60,10 @@ import org.mapstruct.ap.test.builtin.source.IterableSource; import org.mapstruct.ap.test.builtin.source.MapSource; 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.WithJakartaJaxb; +import org.mapstruct.ap.testutil.WithJavaxJaxb; import static org.assertj.core.api.Assertions.assertThat; @@ -77,8 +77,6 @@ MapTarget.class, CalendarProperty.class, DateProperty.class, - JaxbElementListProperty.class, - JaxbElementProperty.class, StringListProperty.class, StringProperty.class, BigDecimalProperty.class, @@ -87,27 +85,16 @@ XmlGregorianCalendarProperty.class, ZonedDateTimeProperty.class, IterableSource.class, - MapSource.class }) -@RunWith(AnnotationProcessorTestRunner.class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@DefaultTimeZone("Europe/Berlin") public class BuiltInTest { - private static TimeZone originalTimeZone; - - @BeforeClass - public static void setDefaultTimeZoneToCet() { - originalTimeZone = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( "Europe/Berlin" ) ); - } - - @AfterClass - public static void restoreOriginalTimeZone() { - TimeZone.setDefault( originalTimeZone ); - } - - @Test - @WithClasses( JaxbMapper.class ) + @ProcessorTest + @WithClasses( { + JaxbMapper.class, + JaxbElementProperty.class, + } ) + @WithJavaxJaxb public void shouldApplyBuiltInOnJAXBElement() { JaxbElementProperty source = new JaxbElementProperty(); source.setProp( createJaxb( "TEST" ) ); @@ -119,8 +106,29 @@ public void shouldApplyBuiltInOnJAXBElement() { assertThat( target.publicProp ).isEqualTo( "PUBLIC TEST" ); } - @Test - @WithClasses( JaxbMapper.class ) + @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" ); + } + + @ProcessorTest + @WithClasses( { + JaxbMapper.class, + JaxbElementProperty.class, + } ) + @WithJavaxJaxb @IssueKey( "1698" ) public void shouldApplyBuiltInOnJAXBElementExtra() { JaxbElementProperty source = new JaxbElementProperty(); @@ -142,10 +150,41 @@ public void shouldApplyBuiltInOnJAXBElementExtra() { assertThat( target2.getProp() ).isNotNull(); } - @Test - @WithClasses( JaxbListMapper.class ) + @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(); + } + + @ProcessorTest + @WithClasses( { + JaxbListMapper.class, + JaxbElementListProperty.class, + } ) + @WithJavaxJaxb @IssueKey( "141" ) - public void shouldApplyBuiltInOnJAXBElementList() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnJAXBElementList() { JaxbElementListProperty source = new JaxbElementListProperty(); source.setProp( createJaxbList( "TEST2" ) ); @@ -157,9 +196,27 @@ public void shouldApplyBuiltInOnJAXBElementList() throws ParseException, Datatyp assertThat( target.publicProp.get( 0 ) ).isEqualTo( "PUBLIC TEST2" ); } - @Test + @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, DatatypeConfigurationException { + public void shouldApplyBuiltInOnDateToXmlGregCal() throws ParseException { DateProperty source = new DateProperty(); source.setProp( createDate( "31-08-1982 10:20:56" ) ); @@ -173,9 +230,9 @@ public void shouldApplyBuiltInOnDateToXmlGregCal() throws ParseException, Dataty assertThat( target.publicProp.toString() ).isEqualTo( "2016-08-31T10:20:56.000+02:00" ); } - @Test + @ProcessorTest @WithClasses( XmlGregCalToDateMapper.class ) - public void shouldApplyBuiltInOnXmlGregCalToDate() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnXmlGregCalToDate() throws DatatypeConfigurationException { XmlGregorianCalendarProperty source = new XmlGregorianCalendarProperty(); source.setProp( createXmlCal( 1999, 3, 2, 60 ) ); @@ -190,9 +247,9 @@ public void shouldApplyBuiltInOnXmlGregCalToDate() throws ParseException, Dataty } - @Test + @ProcessorTest @WithClasses( StringToXmlGregCalMapper.class ) - public void shouldApplyBuiltInStringToXmlGregCal() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInStringToXmlGregCal() { StringProperty source = new StringProperty(); source.setProp( "05.07.1999" ); @@ -225,9 +282,9 @@ public void shouldApplyBuiltInStringToXmlGregCal() throws ParseException, Dataty } - @Test + @ProcessorTest @WithClasses( XmlGregCalToStringMapper.class ) - public void shouldApplyBuiltInXmlGregCalToString() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInXmlGregCalToString() throws DatatypeConfigurationException { XmlGregorianCalendarProperty source = new XmlGregorianCalendarProperty(); source.setProp( createXmlCal( 1999, 3, 2, 60 ) ); @@ -252,9 +309,9 @@ public void shouldApplyBuiltInXmlGregCalToString() throws ParseException, Dataty } - @Test + @ProcessorTest @WithClasses( CalendarToXmlGregCalMapper.class ) - public void shouldApplyBuiltInOnCalendarToXmlGregCal() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnCalendarToXmlGregCal() throws ParseException { CalendarProperty source = new CalendarProperty(); source.setProp( createCalendar( "02.03.1999" ) ); @@ -268,9 +325,9 @@ public void shouldApplyBuiltInOnCalendarToXmlGregCal() throws ParseException, Da assertThat( target.publicProp.toString() ).isEqualTo( "2016-03-02T00:00:00.000+01:00" ); } - @Test + @ProcessorTest @WithClasses( XmlGregCalToCalendarMapper.class ) - public void shouldApplyBuiltInOnXmlGregCalToCalendar() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnXmlGregCalToCalendar() throws DatatypeConfigurationException { XmlGregorianCalendarProperty source = new XmlGregorianCalendarProperty(); source.setProp( createXmlCal( 1999, 3, 2, 60 ) ); @@ -286,9 +343,9 @@ public void shouldApplyBuiltInOnXmlGregCalToCalendar() throws ParseException, Da } - @Test + @ProcessorTest @WithClasses( CalendarToDateMapper.class ) - public void shouldApplyBuiltInOnCalendarToDate() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnCalendarToDate() throws ParseException { CalendarProperty source = new CalendarProperty(); source.setProp( createCalendar( "02.03.1999" ) ); @@ -302,9 +359,9 @@ public void shouldApplyBuiltInOnCalendarToDate() throws ParseException, Datatype assertThat( target.publicProp ).isEqualTo( createCalendar( "02.03.2016" ).getTime() ); } - @Test + @ProcessorTest @WithClasses( DateToCalendarMapper.class ) - public void shouldApplyBuiltInOnDateToCalendar() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnDateToCalendar() throws ParseException { DateProperty source = new DateProperty(); source.setProp( new SimpleDateFormat( "dd.MM.yyyy" ).parse( "02.03.1999" ) ); @@ -319,9 +376,9 @@ public void shouldApplyBuiltInOnDateToCalendar() throws ParseException, Datatype } - @Test + @ProcessorTest @WithClasses( CalendarToStringMapper.class ) - public void shouldApplyBuiltInOnCalendarToString() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnCalendarToString() throws ParseException { CalendarProperty source = new CalendarProperty(); source.setProp( createCalendar( "02.03.1999" ) ); @@ -335,9 +392,9 @@ public void shouldApplyBuiltInOnCalendarToString() throws ParseException, Dataty assertThat( target.publicProp ).isEqualTo( "02.03.2016" ); } - @Test + @ProcessorTest @WithClasses( StringToCalendarMapper.class ) - public void shouldApplyBuiltInOnStringToCalendar() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnStringToCalendar() throws ParseException { StringProperty source = new StringProperty(); source.setProp( "02.03.1999" ); @@ -352,13 +409,13 @@ public void shouldApplyBuiltInOnStringToCalendar() throws ParseException, Dataty } - @Test + @ProcessorTest @WithClasses( IterableSourceTargetMapper.class ) - public void shouldApplyBuiltInOnIterable() throws ParseException, DatatypeConfigurationException { + public void shouldApplyBuiltInOnIterable() throws DatatypeConfigurationException { IterableSource source = new IterableSource(); - source.setDates( Arrays.asList( new XMLGregorianCalendar[] { createXmlCal( 1999, 3, 2, 60 ) } ) ); - source.publicDates = Arrays.asList( new XMLGregorianCalendar[] { createXmlCal( 2016, 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(); @@ -366,14 +423,18 @@ public void shouldApplyBuiltInOnIterable() throws ParseException, DatatypeConfig assertThat( target.publicDates ).containsExactly( "02.03.2016" ); } - @Test - @WithClasses( 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, XMLGregorianCalendar>(); + source.publicExample = new HashMap<>(); source.publicExample.put( createJaxb( "TEST" ), createXmlCal( 2016, 3, 2, 60 ) ); MapTarget target = MapSourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -382,7 +443,7 @@ public void shouldApplyBuiltInOnMap() throws ParseException, DatatypeConfigurati assertThat( target.publicExample.get( "TEST" ) ).isEqualTo( "2016-03-02+01:00" ); } - @Test + @ProcessorTest @WithClasses( CalendarToZonedDateTimeMapper.class ) public void shouldApplyBuiltInOnCalendarToZonedDateTime() throws ParseException { assertThat( CalendarToZonedDateTimeMapper.INSTANCE.map( null ) ).isNull(); @@ -399,7 +460,7 @@ public void shouldApplyBuiltInOnCalendarToZonedDateTime() throws ParseException assertThat( target.publicProp ).isEqualTo( ZonedDateTime.of( 2016, 3, 2, 0, 0, 0, 0, ZoneId.systemDefault() ) ); } - @Test + @ProcessorTest @WithClasses( ZonedDateTimeToCalendarMapper.class ) public void shouldApplyBuiltInOnZonedDateTimeToCalendar() throws ParseException { assertThat( ZonedDateTimeToCalendarMapper.INSTANCE.map( null ) ).isNull(); @@ -417,15 +478,25 @@ public void shouldApplyBuiltInOnZonedDateTimeToCalendar() throws ParseException } 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 ); 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 index fe52452c73..1db8d3abcf 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/builtin/DatatypeFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/builtin/DatatypeFactoryTest.java @@ -11,15 +11,14 @@ import java.util.Date; import java.util.GregorianCalendar; -import org.junit.Test; -import org.junit.runner.RunWith; +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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -31,10 +30,10 @@ DateProperty.class } ) -@RunWith(AnnotationProcessorTestRunner.class) +@DefaultTimeZone("Europe/Berlin") public class DatatypeFactoryTest { - @Test + @ProcessorTest public void testNoConflictsWithOwnDatatypeFactory() throws ParseException { DateProperty source1 = new DateProperty(); 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/jodatime/JodaTimeTest.java b/processor/src/test/java/org/mapstruct/ap/test/builtin/jodatime/JodaTimeTest.java index 9f1e6cef20..cb0c3d2378 100644 --- 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 @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.test.builtin.jodatime; -import static org.assertj.core.api.Assertions.assertThat; - import javax.xml.datatype.DatatypeConstants; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; @@ -16,8 +14,6 @@ import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; -import org.junit.Test; -import org.junit.runner.RunWith; 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; @@ -32,8 +28,12 @@ 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.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.WithJavaxJaxb; +import org.mapstruct.ap.testutil.WithJoda; + +import static org.assertj.core.api.Assertions.assertThat; /** * @@ -46,11 +46,12 @@ LocalDateTimeBean.class, XmlGregorianCalendarBean.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey( "689" ) +@WithJoda +@WithJavaxJaxb public class JodaTimeTest { - @Test + @ProcessorTest @WithClasses(DateTimeToXmlGregorianCalendar.class) public void shouldMapDateTimeToXmlGregorianCalendar() { @@ -69,7 +70,7 @@ public void shouldMapDateTimeToXmlGregorianCalendar() { assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( -60 ); } - @Test + @ProcessorTest @WithClasses(DateTimeToXmlGregorianCalendar.class) public void shouldMapIncompleteDateTimeToXmlGregorianCalendar() { @@ -85,7 +86,7 @@ public void shouldMapIncompleteDateTimeToXmlGregorianCalendar() { assertThat( res.getxMLGregorianCalendar().getMinute() ).isEqualTo( 1 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToDateTime.class) public void shouldMapXmlGregorianCalendarToDateTime() throws Exception { @@ -105,7 +106,7 @@ public void shouldMapXmlGregorianCalendarToDateTime() throws Exception { assertThat( res.getDateTime().getZone().getOffset( null ) ).isEqualTo( 3600000 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToDateTime.class) public void shouldMapXmlGregorianCalendarWithoutTimeZoneToDateTimeWithDefaultTimeZone() throws Exception { @@ -131,7 +132,7 @@ public void shouldMapXmlGregorianCalendarWithoutTimeZoneToDateTimeWithDefaultTim assertThat( res.getDateTime().getZone().getOffset( 0 ) ).isEqualTo( DateTimeZone.getDefault().getOffset( 0 ) ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToDateTime.class) public void shouldMapXmlGregorianCalendarWithoutMillisToDateTime() throws Exception { @@ -157,7 +158,7 @@ public void shouldMapXmlGregorianCalendarWithoutMillisToDateTime() throws Except assertThat( res.getDateTime().getZone().getOffset( null ) ).isEqualTo( 3600000 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToDateTime.class) public void shouldMapXmlGregorianCalendarWithoutMillisAndTimeZoneToDateTimeWithDefaultTimeZone() throws Exception { @@ -182,7 +183,7 @@ public void shouldMapXmlGregorianCalendarWithoutMillisAndTimeZoneToDateTimeWithD assertThat( res.getDateTime().getZone().getOffset( 0 ) ).isEqualTo( DateTimeZone.getDefault().getOffset( 0 ) ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToDateTime.class) public void shouldMapXmlGregorianCalendarWithoutSecondsToDateTime() throws Exception { @@ -207,7 +208,7 @@ public void shouldMapXmlGregorianCalendarWithoutSecondsToDateTime() throws Excep assertThat( res.getDateTime().getZone().getOffset( null ) ).isEqualTo( 3600000 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToDateTime.class) public void shouldMapXmlGregorianCalendarWithoutSecondsAndTimeZoneToDateTimeWithDefaultTimeZone() throws Exception { @@ -231,7 +232,7 @@ public void shouldMapXmlGregorianCalendarWithoutSecondsAndTimeZoneToDateTimeWith assertThat( res.getDateTime().getZone().getOffset( 0 ) ).isEqualTo( DateTimeZone.getDefault().getOffset( 0 ) ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToDateTime.class) public void shouldNotMapXmlGregorianCalendarWithoutMinutes() throws Exception { @@ -247,7 +248,7 @@ public void shouldNotMapXmlGregorianCalendarWithoutMinutes() throws Exception { assertThat( res.getDateTime() ).isNull(); } - @Test + @ProcessorTest @WithClasses({DateTimeToXmlGregorianCalendar.class, XmlGregorianCalendarToDateTime.class}) public void shouldMapRoundTrip() { @@ -263,7 +264,7 @@ public void shouldMapRoundTrip() { } - @Test + @ProcessorTest @WithClasses(LocalDateTimeToXmlGregorianCalendar.class) public void shouldMapLocalDateTimeToXmlGregorianCalendar() { @@ -282,7 +283,7 @@ public void shouldMapLocalDateTimeToXmlGregorianCalendar() { assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); } - @Test + @ProcessorTest @WithClasses(LocalDateTimeToXmlGregorianCalendar.class) public void shouldMapIncompleteLocalDateTimeToXmlGregorianCalendar() { @@ -301,7 +302,7 @@ public void shouldMapIncompleteLocalDateTimeToXmlGregorianCalendar() { assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalDateTime.class) public void shouldMapXmlGregorianCalendarToLocalDateTime() throws Exception { @@ -320,7 +321,7 @@ public void shouldMapXmlGregorianCalendarToLocalDateTime() throws Exception { assertThat( res.getLocalDateTime().getMillisOfSecond() ).isEqualTo( 100 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalDateTime.class) public void shouldMapXmlGregorianCalendarWithoutMillisToLocalDateTime() throws Exception { @@ -344,7 +345,7 @@ public void shouldMapXmlGregorianCalendarWithoutMillisToLocalDateTime() throws E assertThat( res.getLocalDateTime().getMillisOfSecond() ).isEqualTo( 0 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalDateTime.class) public void shouldMapXmlGregorianCalendarWithoutSecondsToLocalDateTime() throws Exception { @@ -368,7 +369,7 @@ public void shouldMapXmlGregorianCalendarWithoutSecondsToLocalDateTime() throws assertThat( res.getLocalDateTime().getMillisOfSecond() ).isEqualTo( 0 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalDateTime.class) public void shouldNotMapXmlGregorianCalendarWithoutMinutesToLocalDateTime() throws Exception { @@ -385,7 +386,7 @@ public void shouldNotMapXmlGregorianCalendarWithoutMinutesToLocalDateTime() thro } - @Test + @ProcessorTest @WithClasses(LocalDateToXmlGregorianCalendar.class) public void shouldMapLocalDateToXmlGregorianCalendar() { @@ -404,7 +405,7 @@ public void shouldMapLocalDateToXmlGregorianCalendar() { assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalDate.class) public void shouldMapXmlGregorianCalendarToLocalDate() throws Exception { @@ -422,7 +423,7 @@ public void shouldMapXmlGregorianCalendarToLocalDate() throws Exception { assertThat( res.getLocalDate().getDayOfMonth() ).isEqualTo( 25 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalDate.class) public void shouldNotMapXmlGregorianCalendarWithoutDaysToLocalDate() throws Exception { @@ -439,7 +440,7 @@ public void shouldNotMapXmlGregorianCalendarWithoutDaysToLocalDate() throws Exce } - @Test + @ProcessorTest @WithClasses(LocalTimeToXmlGregorianCalendar.class) public void shouldMapIncompleteLocalTimeToXmlGregorianCalendar() { @@ -458,7 +459,7 @@ public void shouldMapIncompleteLocalTimeToXmlGregorianCalendar() { assertThat( res.getxMLGregorianCalendar().getTimezone() ).isEqualTo( DatatypeConstants.FIELD_UNDEFINED ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalTime.class) public void shouldMapXmlGregorianCalendarToLocalTime() throws Exception { @@ -474,7 +475,7 @@ public void shouldMapXmlGregorianCalendarToLocalTime() throws Exception { assertThat( res.getLocalTime().getMillisOfSecond() ).isEqualTo( 100 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalTime.class) public void shouldMapXmlGregorianCalendarWithoutMillisToLocalTime() throws Exception { @@ -492,7 +493,7 @@ public void shouldMapXmlGregorianCalendarWithoutMillisToLocalTime() throws Excep assertThat( res.getLocalTime().getMillisOfSecond() ).isEqualTo( 0 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalTime.class) public void shouldMapXmlGregorianCalendarWithoutSecondsToLocalTime() throws Exception { @@ -510,7 +511,7 @@ public void shouldMapXmlGregorianCalendarWithoutSecondsToLocalTime() throws Exce assertThat( res.getLocalTime().getMillisOfSecond() ).isEqualTo( 0 ); } - @Test + @ProcessorTest @WithClasses(XmlGregorianCalendarToLocalTime.class) public void shouldNotMapXmlGregorianCalendarWithoutMinutesToLocalTime() throws Exception { 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/callbacks/BaseMapper.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/BaseMapper.java index bee462accc..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 @@ -24,7 +24,7 @@ public abstract class BaseMapper { @Qualified public abstract Target sourceToTargetQualified(Source source); - 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/CallbackMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/CallbackMethodTest.java index 91a84655fa..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 @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.test.callbacks; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -14,34 +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.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() ); @@ -49,7 +46,7 @@ public void callbackMethodsForBeanMappingCalled() { assertBeanMappingInvocations( BaseMapper.getInvocations() ); } - @Test + @ProcessorTest public void callbackMethodsForBeanMappingWithResultParamCalled() { SourceTargetMapper.INSTANCE.sourceToTarget( createSource(), createEmptyTarget() ); @@ -57,7 +54,7 @@ public void callbackMethodsForBeanMappingWithResultParamCalled() { assertBeanMappingInvocations( BaseMapper.getInvocations() ); } - @Test + @ProcessorTest public void callbackMethodsForIterableMappingCalled() { SourceTargetCollectionMapper.INSTANCE.sourceToTarget( Arrays.asList( createSource() ) ); @@ -65,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() ) ); @@ -82,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 ); @@ -109,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 ) ); @@ -173,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; } @@ -191,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 ) ); @@ -200,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 ), @@ -211,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 ), @@ -226,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 762247045d..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 @@ -18,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 e408852a0c..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 @@ -62,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/Source.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Source.java index 7194ecc2a4..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 @@ -40,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/Target.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/Target.java index 90edbe462f..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 @@ -40,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/ongeneratedmethods/CompanyMapperPostProcessing.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/CompanyMapperPostProcessing.java index dd18e48aa6..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 @@ -17,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/MappingResultPostprocessorTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/ongeneratedmethods/MappingResultPostprocessorTest.java index 19feaefda2..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 @@ -5,15 +5,13 @@ */ package org.mapstruct.ap.test.callbacks.ongeneratedmethods; -import static org.assertj.core.api.Assertions.assertThat; - 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.assertj.core.api.Assertions.assertThat; /** * @@ -31,10 +29,9 @@ CompanyMapperPostProcessing.class }) @IssueKey("183") -@RunWith(AnnotationProcessorTestRunner.class) public class MappingResultPostprocessorTest { - @Test + @ProcessorTest public void test() { // setup @@ -44,7 +41,7 @@ public void test() { 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/CallbacksWithReturnValuesTest.java b/processor/src/test/java/org/mapstruct/ap/test/callbacks/returning/CallbacksWithReturnValuesTest.java index bea89fa7f9..10d2a29cd7 100644 --- 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 @@ -5,17 +5,17 @@ */ package org.mapstruct.ap.test.callbacks.returning; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; -import org.junit.runner.RunWith; +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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +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 @@ -26,29 +26,31 @@ @WithClasses( { Attribute.class, AttributeDto.class, Node.class, NodeDto.class, NodeMapperDefault.class, NodeMapperWithContext.class, NodeMapperContext.class, Number.class, NumberMapperDefault.class, NumberMapperContext.class, NumberMapperWithContext.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) -public class CallbacksWithReturnValuesTest { - @Test( expected = StackOverflowError.class ) - public void mappingWithDefaultHandlingRaisesStackOverflowError() { +class CallbacksWithReturnValuesTest { + @AfterEach + void cleanup() { + NumberMapperContext.clearCache(); + NumberMapperContext.clearVisited(); + } + + @ProcessorTest + void mappingWithDefaultHandlingRaisesStackOverflowError() { Node root = buildNodes(); - NodeMapperDefault.INSTANCE.nodeToNodeDto( root ); + assertThatThrownBy( () -> NodeMapperDefault.INSTANCE.nodeToNodeDto( root ) ) + .isInstanceOf( StackOverflowError.class ); } - @Test( expected = StackOverflowError.class ) - public void updatingWithDefaultHandlingRaisesStackOverflowError() { + @ProcessorTest + void updatingWithDefaultHandlingRaisesStackOverflowError() { Node root = buildNodes(); - NodeMapperDefault.INSTANCE.nodeToNodeDto( root, new NodeDto() ); + assertThatThrownBy( () -> NodeMapperDefault.INSTANCE.nodeToNodeDto( root, new NodeDto() ) ) + .isInstanceOf( StackOverflowError.class ); } - @Test - public void mappingWithContextCorrectlyResolvesCycles() { - final AtomicReference contextLevel = new AtomicReference( null ); - ContextListener contextListener = new ContextListener() { - @Override - public void methodCalled(Integer level, String method, Object source, Object target) { - contextLevel.set( level ); - } - }; + @ProcessorTest + void mappingWithContextCorrectlyResolvesCycles() { + final AtomicReference contextLevel = new AtomicReference<>( null ); + ContextListener contextListener = (level, method, source, target) -> contextLevel.set( level ); NodeMapperContext.addContextListener( contextListener ); try { @@ -74,29 +76,50 @@ private static Node buildNodes() { return root; } - @Test - public void numberMappingWithoutContextDoesNotUseCache() { + @ProcessorTest + void numberMappingWithoutContextDoesNotUseCache() { Number n1 = NumberMapperDefault.INSTANCE.integerToNumber( 2342 ); Number n2 = NumberMapperDefault.INSTANCE.integerToNumber( 2342 ); + assertThat( n1 ).isEqualTo( n2 ); assertThat( n1 ).isNotSameAs( n2 ); } - @Test - public void numberMappingWithContextUsesCache() { + @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 ); - NumberMapperContext.clearCache(); } - @Test - public void numberMappingWithContextCallsVisitNumber() { + @ProcessorTest + void numberMappingWithContextCallsVisitNumber() { Number n1 = NumberMapperWithContext.INSTANCE.integerToNumber( 1234 ); Number n2 = NumberMapperWithContext.INSTANCE.integerToNumber( 5678 ); + assertThat( NumberMapperContext.getVisited() ).isEqualTo( Arrays.asList( n1, n2 ) ); - NumberMapperContext.clearVisited(); + } + + @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 index c939ec7d3d..2ee619c61d 100644 --- 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 @@ -25,8 +25,8 @@ public Node() { public Node(String name) { this.name = name; - this.children = new ArrayList(); - this.attributes = new ArrayList(); + this.children = new ArrayList<>(); + this.attributes = new ArrayList<>(); } public Node getParent() { 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 index def62dab8d..325778b85e 100644 --- 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 @@ -19,11 +19,11 @@ * @author Pascal Grün */ public class NodeMapperContext { - private static final ThreadLocal LEVEL = new ThreadLocal(); - private static final ThreadLocal> MAPPING = new ThreadLocal>(); + 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 static final List LISTENERS = new CopyOnWriteArrayList<>(); private NodeMapperContext() { // Only allow static access @@ -48,7 +48,7 @@ public static void setInstance(Object source, @MappingTarget Object target) { fireMethodCalled( level, "setInstance", source, target ); if ( level == null ) { LEVEL.set( 1 ); - MAPPING.set( new IdentityHashMap() ); + MAPPING.set( new IdentityHashMap<>() ); } else { LEVEL.set( level + 1 ); 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 index 7ce3510775..bbbe9c6bec 100644 --- 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 @@ -19,9 +19,9 @@ * @author Pascal Grün */ public class NumberMapperContext { - private static final Map CACHE = new HashMap(); + private static final Map CACHE = new HashMap<>(); - private static final List VISITED = new ArrayList(); + private static final List VISITED = new ArrayList<>(); private NumberMapperContext() { // Only allow static access @@ -45,8 +45,7 @@ public static void clearVisited() { @AfterMapping public static Number getInstance(Integer source, @MappingTarget Number target) { - Number cached = CACHE.get( target ); - return ( cached == null ? null : cached ); + return CACHE.get( target ); } @AfterMapping 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 e92dec0105..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 @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.callbacks.typematching; -import static org.assertj.core.api.Assertions.assertThat; - -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.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/collection/CollectionMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/CollectionMappingTest.java index 8f752ec497..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 @@ -5,9 +5,6 @@ */ package org.mapstruct.ap.test.collection; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; @@ -17,20 +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; + +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 }) -@RunWith(AnnotationProcessorTestRunner.class) public class CollectionMappingTest { - @Test + @ProcessorTest @IssueKey("6") public void shouldMapNullList() { Source source = new Source(); @@ -41,7 +38,7 @@ public void shouldMapNullList() { assertThat( target.getStringList() ).isNull(); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapNullList() { Target target = new Target(); @@ -52,7 +49,7 @@ public void shouldReverseMapNullList() { assertThat( source.getStringList() ).isNull(); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapList() { Source source = new Source(); @@ -64,7 +61,7 @@ public void shouldMapList() { assertThat( target.getStringList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("92") public void shouldMapListWithoutSetter() { Source source = new Source(); @@ -76,7 +73,7 @@ public void shouldMapListWithoutSetter() { assertThat( target.getStringListNoSetter() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapList() { Target target = new Target(); @@ -88,7 +85,7 @@ public void shouldReverseMapList() { assertThat( source.getStringList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapListAsCopy() { Source source = new Source(); @@ -101,7 +98,7 @@ public void shouldMapListAsCopy() { assertThat( source.getStringList() ).isNotEqualTo( target.getStringList() ); } - @Test + @ProcessorTest @IssueKey( "153" ) public void shouldMapListWithClearAndAddAll() { Source source = new Source(); @@ -113,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 ); @@ -130,7 +127,7 @@ public void shouldMapListWithClearAndAddAll() { TestList.setAddAllCalled( false ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapListAsCopy() { Target target = new Target(); @@ -142,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 ); @@ -154,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 ); @@ -166,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 ); @@ -178,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 ); @@ -190,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" ); @@ -202,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" ); @@ -214,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" ); @@ -226,7 +223,7 @@ public void shouldReverseMapSetAsCopy() { assertThat( target.getStringSet() ).containsOnly( "Bob", "Alice" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapListToCollection() { Source source = new Source(); @@ -238,7 +235,7 @@ public void shouldMapListToCollection() { assertThat( target.getIntegerCollection() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapListToCollection() { Target target = new Target(); @@ -250,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 ); @@ -262,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 ); @@ -274,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 ); @@ -286,7 +283,7 @@ public void shouldReverseMapIntegerSetToStringSet() { assertThat( source.getAnotherIntegerSet() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldMapSetOfEnumToStringSet() { Source source = new Source(); @@ -298,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 ); @@ -310,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 ); @@ -326,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 ); @@ -345,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 ); @@ -358,17 +355,17 @@ 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 ); } - @Test + @ProcessorTest @IssueKey("732") public void shouldEnumSetAsCopy() { Source source = new Source(); @@ -381,11 +378,11 @@ public void shouldEnumSetAsCopy() { assertThat( target.getEnumSet() ).containsOnly( Colour.BLUE, Colour.GREEN ); } - @Test + @ProcessorTest @IssueKey("853") public void shouldMapNonGenericList() { Source source = new Source(); - source.setStringList3( new ArrayList( Arrays.asList( "Bob", "Alice" ) ) ); + source.setStringList3( new ArrayList<>( Arrays.asList( "Bob", "Alice" ) ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -406,12 +403,11 @@ public void shouldMapNonGenericList() { assertThat( mappedSource.getStringList3() ).containsExactly( "Bill", "Bob" ); } - @SuppressWarnings("unchecked") - @Test + @ProcessorTest @IssueKey("853") public void shouldMapNonGenericMap() { Source source = new Source(); - Map map = new HashMap(); + Map map = new HashMap<>(); map.put( "Bob", 123L ); map.put( "Alice", 456L ); source.setStringLongMapForNonGeneric( map ); 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 d716918d78..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 @@ -21,13 +21,13 @@ public abstract class SourceTargetMapper { 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(source = "stringList3", target = "nonGenericStringList"), - @Mapping(source = "stringLongMapForNonGeneric", target = "nonGenericMapStringtoLong") + @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") }) public abstract Target sourceToTarget(Source source); @@ -37,8 +37,6 @@ public abstract class SourceTargetMapper { @InheritConfiguration public abstract Target sourceToTargetTwoArg(Source source, @MappingTarget Target target); - public abstract Set integerSetToStringSet(Set integers); - @InheritInverseConfiguration public abstract Set stringSetToIntegerSet(Set strings); 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 index 36cb8f38db..af3539ff9e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolder.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/StringHolder.java @@ -41,13 +41,10 @@ public boolean equals(Object obj) { } StringHolder other = (StringHolder) obj; if ( string == null ) { - if ( other.string != null ) { - return false; - } + return other.string == null; } - else if ( !string.equals( other.string ) ) { - return false; + else { + return string.equals( other.string ); } - return true; } } 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 e4e445962f..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 @@ -8,14 +8,12 @@ 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 @@ -54,10 +52,11 @@ public class Target { 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() { @@ -144,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; } 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 1507f56399..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 @@ -5,16 +5,10 @@ */ package org.mapstruct.ap.test.collection.adder; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - import java.util.ArrayList; import java.util.Arrays; -import org.junit.Rule; -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; @@ -34,10 +28,15 @@ 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.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 */ @@ -70,18 +69,17 @@ Source2Target2Mapper.class, Foo.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class AdderTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @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 ); @@ -95,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(); @@ -134,7 +134,7 @@ public void testAddwithExistingTarget() throws DogException { assertTrue( AdderUsageObserver.isUsed() ); } - @Test + @ProcessorTest public void testShouldUseDefaultStrategy() throws DogException { AdderUsageObserver.setUsed( false ); @@ -148,7 +148,7 @@ public void testShouldUseDefaultStrategy() throws DogException { assertFalse( AdderUsageObserver.isUsed() ); } - @Test + @ProcessorTest public void testShouldPreferSetterStrategyButThereIsNone() throws DogException { AdderUsageObserver.setUsed( false ); @@ -162,7 +162,7 @@ public void testShouldPreferSetterStrategyButThereIsNone() throws DogException { assertTrue( AdderUsageObserver.isUsed() ); } - @Test + @ProcessorTest public void testShouldPreferHumanSingular() { AdderUsageObserver.setUsed( false ); @@ -177,8 +177,8 @@ public void testShouldPreferHumanSingular() { assertTrue( AdderUsageObserver.isUsed() ); } - @Test - public void testshouldFallBackToDaliSingularInAbsenseOfHumanSingular() { + @ProcessorTest + public void testShouldFallBackToDaliSingularInAbsenceOfHumanSingular() { AdderUsageObserver.setUsed( false ); SourceTeeth source = new SourceTeeth(); @@ -191,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(); @@ -204,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 ); @@ -218,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(); @@ -234,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(); @@ -249,8 +249,8 @@ public void testSingleElementSource() throws DogException { } @IssueKey( "310" ) - @Test - public void testMissingImport() throws DogException { + @ProcessorTest + public void testMissingImport() { generatedSource.addComparisonToFixtureFor( Source2Target2Mapper.class ); Source2 source = new Source2(); @@ -262,7 +262,7 @@ public void testMissingImport() throws DogException { } @IssueKey("1478") - @Test + @ProcessorTest public void useIterationNameFromSource() { generatedSource.addComparisonToFixtureFor( SourceTargetMapperWithDifferentProperties.class ); 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 9cb74c7dc9..ef80c4b6c0 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 @@ -31,7 +31,6 @@ public class PetMapper { .put( 3L, "cat" ) .put( 4L, "dog" ).build(); - /** * method to be used when using an adder * @@ -63,7 +62,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 ) ); } @@ -82,7 +81,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/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/SourceTargetMapper.java index 5e3017c19a..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 @@ -42,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/_target/Target.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/_target/Target.java index 022d593650..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 @@ -24,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 850c6ee94d..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 @@ -16,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 3d437b3ab7..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 @@ -26,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 8117abf2b4..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 @@ -26,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 1e21e819ca..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 @@ -20,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 0840e0115e..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 @@ -26,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 index 5005251235..10770922bd 100644 --- 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 @@ -13,7 +13,7 @@ */ public class TargetWithAnimals { - private List animals = new ArrayList(); + private List animals = new ArrayList<>(); public List getAnimals() { return animals; 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 69613854d0..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 @@ -22,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/SingleElementSource.java b/processor/src/test/java/org/mapstruct/ap/test/collection/adder/source/SingleElementSource.java index 91e277a209..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 @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.collection.adder.source; - /** * @author Sjaak Derksen */ 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 b5de8abaab..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 @@ -5,9 +5,6 @@ */ package org.mapstruct.ap.test.collection.defaultimplementation; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -23,14 +20,15 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentNavigableMap; -import org.junit.Rule; -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, Target.class, @@ -38,14 +36,13 @@ TargetFoo.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class DefaultCollectionImplementationTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource() + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() .addComparisonToFixtureFor( SourceTargetMapper.class ); - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForConcurrentMap() { ConcurrentMap target = @@ -54,7 +51,7 @@ public void shouldUseDefaultImplementationForConcurrentMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForConcurrentNavigableMap() { ConcurrentNavigableMap target = @@ -63,7 +60,7 @@ public void shouldUseDefaultImplementationForConcurrentNavigableMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForMap() { Map target = SourceTargetMapper.INSTANCE.sourceFooMapToTargetFooMap( createSourceFooMap() ); @@ -71,7 +68,7 @@ public void shouldUseDefaultImplementationForMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForNavigableMap() { NavigableMap target = @@ -80,7 +77,7 @@ public void shouldUseDefaultImplementationForNavigableMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForSortedMap() { SortedMap target = @@ -89,7 +86,7 @@ public void shouldUseDefaultImplementationForSortedMap() { assertResultMap( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForNaviableSet() { NavigableSet target = @@ -98,7 +95,7 @@ public void shouldUseDefaultImplementationForNaviableSet() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForCollection() { Collection target = @@ -107,7 +104,7 @@ public void shouldUseDefaultImplementationForCollection() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForIterable() { Iterable target = @@ -116,7 +113,7 @@ public void shouldUseDefaultImplementationForIterable() { assertResultList( target ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldUseDefaultImplementationForList() { List target = SourceTargetMapper.INSTANCE.sourceFoosToTargetFoos( createSourceFooList() ); @@ -124,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 = @@ -142,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() @@ -154,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(); @@ -189,7 +200,7 @@ private void assertResultMap(Map result) { } 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 914a5e3f01..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 @@ -5,34 +5,31 @@ */ package org.mapstruct.ap.test.collection.defaultimplementation; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - import java.util.Arrays; 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.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" ); 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 562966dfd1..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 @@ -15,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/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java index 1143a311ad..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 @@ -26,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 8a5cc6200c..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 @@ -14,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 da76cc4134..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 @@ -45,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/ErroneousCollectionMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionMappingTest.java index feefb92b40..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 @@ -7,45 +7,46 @@ 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 = 15, - messageRegExp = "Can't generate mapping method from iterable type to non-iterable type"), + 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, - messageRegExp = "Can't generate mapping method from non-iterable type to iterable type") + message = "Can't generate mapping method from non-iterable type to iterable type from java stdlib."), + @Diagnostic(type = ErroneousCollectionToNonCollectionMapper.class, + kind = Kind.ERROR, + 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( @@ -54,15 +55,14 @@ public void shouldFailToGenerateImplementationBetweenCollectionAndNonCollection( @Diagnostic(type = ErroneousCollectionToPrimitivePropertyMapper.class, kind = Kind.ERROR, line = 13, - messageRegExp = "Can't map property \"java.util.List strings\" to \"int strings\". " - + "Consider to declare/implement a mapping method: \"int map\\(java.util.List" - + " value\\)\"") + message = "Can't map property \"List strings\" to \"int strings\". " + + "Consider to declare/implement a mapping method: \"int map(List value)\".") } ) public void shouldFailToGenerateImplementationBetweenCollectionAndPrimitive() { } - @Test + @ProcessorTest @IssueKey("417") @WithClasses({ EmptyItererableMappingMapper.class }) @ExpectedCompilationOutcome( @@ -71,14 +71,14 @@ public void shouldFailToGenerateImplementationBetweenCollectionAndPrimitive() { @Diagnostic(type = EmptyItererableMappingMapper.class, kind = Kind.ERROR, line = 22, - messageRegExp = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + 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( @@ -87,7 +87,7 @@ public void shouldFailOnEmptyIterableAnnotation() { @Diagnostic(type = EmptyMapMappingMapper.class, kind = Kind.ERROR, line = 22, - messageRegExp = "'nullValueMappingStrategy', 'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', " + message = "'nullValueMappingStrategy', 'keyDateFormat', 'keyQualifiedBy', 'keyTargetType', " + "'valueDateFormat', 'valueQualfiedBy' and 'valueTargetType' are all undefined in @MapMapping, " + "define at least one of them.") } @@ -95,7 +95,7 @@ public void shouldFailOnEmptyIterableAnnotation() { public void shouldFailOnEmptyMapAnnotation() { } - @Test + @ProcessorTest @IssueKey("459") @WithClasses({ ErroneousCollectionNoElementMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( @@ -104,15 +104,15 @@ public void shouldFailOnEmptyMapAnnotation() { @Diagnostic(type = ErroneousCollectionNoElementMappingFound.class, kind = Kind.ERROR, line = 25, - messageRegExp = "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\\)") + 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( @@ -121,15 +121,14 @@ public void shouldFailOnNoElementMappingFound() { @Diagnostic(type = ErroneousCollectionNoElementMappingFoundDisabledAuto.class, kind = Kind.ERROR, line = 19, - messageRegExp = - "Can't map collection element \".*AttributedString\" to \".*String \". " + - "Consider to declare/implement a mapping method: \".*String map\\(.*AttributedString value\\)") + message = "Can't map collection element \"AttributedString\" to \"String \". " + + "Consider to declare/implement a mapping method: \"String map(AttributedString value)\".") } ) public void shouldFailOnNoElementMappingFoundWithDisabledAuto() { } - @Test + @ProcessorTest @IssueKey("459") @WithClasses({ ErroneousCollectionNoKeyMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( @@ -138,16 +137,15 @@ public void shouldFailOnNoElementMappingFoundWithDisabledAuto() { @Diagnostic(type = ErroneousCollectionNoKeyMappingFound.class, kind = Kind.ERROR, line = 25, - messageRegExp = "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\\)" ) + 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( @@ -156,14 +154,14 @@ public void shouldFailOnNoKeyMappingFound() { @Diagnostic(type = ErroneousCollectionNoKeyMappingFoundDisabledAuto.class, kind = Kind.ERROR, line = 19, - messageRegExp = "Can't map map key \".*AttributedString\" to \".*String \". " + - "Consider to declare/implement a mapping method: \".*String map\\(.*AttributedString value\\)") + message = "Can't map map key \"AttributedString\" to \"String \". Consider to " + + "declare/implement a mapping method: \"String map(AttributedString value)\".") } ) public void shouldFailOnNoKeyMappingFoundWithDisabledAuto() { } - @Test + @ProcessorTest @IssueKey("459") @WithClasses({ ErroneousCollectionNoValueMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( @@ -172,16 +170,15 @@ public void shouldFailOnNoKeyMappingFoundWithDisabledAuto() { @Diagnostic(type = ErroneousCollectionNoValueMappingFound.class, kind = Kind.ERROR, line = 25, - messageRegExp = "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\\)" ) + 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() { } - @Test + @ProcessorTest @IssueKey("993") @WithClasses({ ErroneousCollectionNoValueMappingFoundDisabledAuto.class }) @ExpectedCompilationOutcome( @@ -190,8 +187,8 @@ public void shouldFailOnNoValueMappingFound() { @Diagnostic(type = ErroneousCollectionNoValueMappingFoundDisabledAuto.class, kind = Kind.ERROR, line = 19, - messageRegExp = "Can't map map value \".*AttributedString\" to \".*String \". " + - "Consider to declare/implement a mapping method: \".*String map(.*AttributedString value)") + 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/ErroneousCollectionToNonCollectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/erroneous/ErroneousCollectionToNonCollectionMapper.java index bbd56a3dd4..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 @@ -15,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/forged/CollectionMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/forged/CollectionMappingTest.java index c538318fd7..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 @@ -5,24 +5,19 @@ */ package org.mapstruct.ap.test.collection.forged; - -import static org.assertj.core.api.Assertions.assertThat; - 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.assertj.core.api.Assertions.assertThat; /** * Test for mappings between collection types, @@ -30,10 +25,9 @@ * @author Sjaak Derksen */ @IssueKey( "4" ) -@RunWith(AnnotationProcessorTestRunner.class) public class CollectionMappingTest { - @Test + @ProcessorTest @WithClasses({ CollectionMapper.class, Source.class, Target.class }) public void shouldForgeNewIterableMappingMethod() { @@ -52,7 +46,7 @@ public void shouldForgeNewIterableMappingMethod() { assertThat( source2.publicFooSet ).isEqualTo( Collections.asSet( "3", "4" ) ); } - @Test + @ProcessorTest @WithClasses({ CollectionMapper.class, Source.class, Target.class }) public void shouldForgeNewMapMappingMethod() { @@ -74,8 +68,9 @@ public void shouldForgeNewMapMappingMethod() { assertThat( source2.publicBarMap ).isEqualTo( source.publicBarMap ); } - @Test - @WithClasses({ ErroneousCollectionNonMappableSetMapper.class, + @ProcessorTest + @WithClasses({ + ErroneousCollectionNonMappableSetMapper.class, ErroneousNonMappableSetSource.class, ErroneousNonMappableSetTarget.class, Foo.class, @@ -87,14 +82,15 @@ public void shouldForgeNewMapMappingMethod() { @Diagnostic(type = ErroneousCollectionNonMappableSetMapper.class, kind = Kind.ERROR, line = 17, - messageRegExp = "No target bean properties found: can't map Collection element \".* nonMappableSet\" " - + "to \".* nonMappableSet\". Consider to declare/implement a mapping method: .*." ), + 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, @@ -107,21 +103,21 @@ public void shouldGenerateNonMappleMethodForSetMapping() { @Diagnostic(type = ErroneousCollectionNonMappableMapMapper.class, kind = Kind.ERROR, line = 17, - messageRegExp = "No target bean properties found: can't map Map key \".* nonMappableMap\\{:key\\}\" " - + "to \".* " - + "nonMappableMap\\{:key\\}\". Consider to declare/implement a mapping method: .*." ), + 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, - messageRegExp = "No target bean properties found: can't map Map value \".* " - + "nonMappableMap\\{:value\\}\" to \".* nonMappableMap\\{:value\\}\". " - + "Consider to declare/implement a mapping method: .*." ), + 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() { @@ -141,7 +137,7 @@ public void shouldForgeNewIterableMappingMethodReturnNullOnNullSource() { assertThat( source2.publicFooSet ).isNull(); } - @Test + @ProcessorTest @IssueKey( "640" ) @WithClasses({ CollectionMapper.class, Source.class, Target.class }) public void shouldForgeNewMapMappingMethodReturnNullOnNullSource() { @@ -161,7 +157,7 @@ public void shouldForgeNewMapMappingMethodReturnNullOnNullSource() { assertThat( source2.publicBarMap ).isNull(); } - @Test + @ProcessorTest @IssueKey( "640" ) @WithClasses({ CollectionMapperNullValueMappingReturnDefault.class, Source.class, Target.class }) public void shouldForgeNewIterableMappingMethodReturnEmptyOnNullSource() { @@ -183,7 +179,7 @@ public void shouldForgeNewIterableMappingMethodReturnEmptyOnNullSource() { assertThat( source2.publicBarMap ).isEmpty(); } - @Test + @ProcessorTest @IssueKey( "640" ) @WithClasses({ CollectionMapperNullValueMappingReturnDefault.class, Source.class, Target.class }) public void shouldForgeNewMapMappingMethodReturnEmptyOnNullSource() { 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 index bbf3a6d24e..1f2f2f00b7 100644 --- 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 @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.collection.immutabletarget; +import java.util.ArrayList; import java.util.List; /** @@ -13,7 +14,7 @@ */ public class CupboardEntityOnlyGetter { - private List content; + private List content = new ArrayList<>(); public List getContent() { return content; 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/ErroneousCupboardMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java deleted file mode 100644 index 028ac4d765..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/immutabletarget/ErroneousCupboardMapper.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright MapStruct 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 ErroneousCupboardMapper { - - ErroneousCupboardMapper INSTANCE = Mappers.getMapper( ErroneousCupboardMapper.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 index f196a1ed98..8097dfdfe9 100644 --- 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 @@ -5,36 +5,33 @@ */ package org.mapstruct.ap.test.collection.immutabletarget; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; import java.util.Collections; -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; /** * * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({CupboardDto.class, CupboardEntity.class, CupboardMapper.class}) @IssueKey( "1126" ) public class ImmutableProductTest { - @Test + @ProcessorTest public void shouldHandleImmutableTarget() { CupboardDto in = new CupboardDto(); in.setContent( Arrays.asList( "cups", "soucers" ) ); CupboardEntity out = new CupboardEntity(); - out.setContent( Collections.emptyList() ); + out.setContent( Collections.emptyList() ); CupboardMapper.INSTANCE.map( in, out ); @@ -42,21 +39,26 @@ public void shouldHandleImmutableTarget() { assertThat( out.getContent() ).containsExactly( "cups", "soucers" ); } - @Test + @ProcessorTest @WithClasses({ - ErroneousCupboardMapper.class, + CupboardNoSetterMapper.class, CupboardEntityOnlyGetter.class }) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diagnostics = { - @Diagnostic(type = ErroneousCupboardMapper.class, - kind = javax.tools.Diagnostic.Kind.ERROR, + @Diagnostic(type = CupboardNoSetterMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, line = 22, - messageRegExp = "No write accessor found for property \"content\" in target type.") - } - ) - public void testShouldFailOnPropertyMappingNoPropertySetterOnlyGetter() { + 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..915b9e5fd4 --- /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 org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +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 b852a0a4d8..892e24df4c 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 @@ -8,18 +8,17 @@ import static org.assertj.core.api.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) +@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(); @@ -32,7 +31,7 @@ public void shouldMapStringListToStringUsingCustomMapper() { assertThat( target.publicNames ).isEqualTo( "Alice-Bob-Jim" ); } - @Test + @ProcessorTest @IssueKey("6") public void shouldReverseMapStringListToStringUsingCustomMapper() { Target target = new Target(); @@ -45,4 +44,26 @@ public void shouldReverseMapStringListToStringUsingCustomMapper() { 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/StringListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/iterabletononiterable/StringListMapper.java index 1cbe6e128d..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 @@ -8,12 +8,10 @@ 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/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..8b2373d4d4 --- /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 org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.stream.Collectors; + +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/MapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java index 45ccbb911e..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 @@ -5,20 +5,20 @@ */ package org.mapstruct.ap.test.collection.map; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - +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.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; /** * Test for implementation of {@code Map} mapping methods. @@ -27,14 +27,14 @@ */ @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 ); @@ -46,7 +46,7 @@ public void shouldCreateMapMethodImplementation() { ); } - @Test + @ProcessorTest public void shouldCreateReverseMapMethodImplementation() { Map values = createStringStringMap(); @@ -55,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 ); @@ -84,31 +84,45 @@ 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 ).contains( - entry( 42L, new GregorianCalendar( 1980, 0, 1 ).getTime() ), - entry( 121L, new GregorianCalendar( 2013, 6, 20 ).getTime() ) + 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 ) ); + source.setPublicValues( new HashMap<>( values ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -129,13 +143,13 @@ public void shouldInvokeMapMethodImplementationForMapTypedProperty() { ); } - @Test + @ProcessorTest public void shouldInvokeReverseMapMethodImplementationForMapTypedProperty() { Map values = createStringStringMap(); Target target = new Target(); target.setValues( values ); - target.publicValues = new HashMap( values ); + target.publicValues = new HashMap<>( values ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -143,27 +157,27 @@ public void shouldInvokeReverseMapMethodImplementationForMapTypedProperty() { assertThat( source.getValues() ).isNotNull(); assertThat( source.getValues() ).hasSize( 2 ); assertThat( source.getValues() ).contains( - entry( 42L, new GregorianCalendar( 1980, 0, 1 ).getTime() ), - entry( 121L, new GregorianCalendar( 2013, 6, 20 ).getTime() ) + 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, 0, 1 ).getTime() ), - entry( 121L, new GregorianCalendar( 2013, 6, 20 ).getTime() ) + 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/wildcard/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/BeanMapper.java index 146c876871..0fd26e11fc 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 @@ -29,7 +29,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/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Idea.java index fc9b89b571..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 @@ -11,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/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/Plan.java index 29fcfc2f3a..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 @@ -11,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/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/wildcard/WildCardTest.java index 3e6da19cb8..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 @@ -5,37 +5,34 @@ */ package org.mapstruct.ap.test.collection.wildcard; -import static org.assertj.core.api.Assertions.assertThat; - import java.math.BigDecimal; - import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; -import org.junit.Rule; -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. * * @author Sjaak Derksen */ @IssueKey("527") -@RunWith(AnnotationProcessorTestRunner.class) public class WildCardTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest @WithClasses({ ExtendsBoundSourceTargetMapper.class, ExtendsBoundSource.class, @@ -57,7 +54,7 @@ public void shouldGenerateExtendsBoundSourceForgedIterableMethod() { .doesNotContain( "? extends org.mapstruct.ap.test.collection.wildcard.Idea" ); } - @Test + @ProcessorTest @WithClasses({ SourceSuperBoundTargetMapper.class, Source.class, @@ -79,7 +76,7 @@ public void shouldGenerateSuperBoundTargetForgedIterableMethod() { .doesNotContain( "? super org.mapstruct.ap.test.collection.wildcard.Idea" ); } - @Test + @ProcessorTest @WithClasses({ ErroneousIterableSuperBoundSourceMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -87,13 +84,13 @@ public void shouldGenerateSuperBoundTargetForgedIterableMethod() { @Diagnostic( type = ErroneousIterableSuperBoundSourceMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, - messageRegExp = "Can't generate mapping method for a wildcard super bound source." ) + message = "Can't generate mapping method for a wildcard super bound source." ) } ) public void shouldFailOnSuperBoundSource() { } - @Test + @ProcessorTest @WithClasses({ ErroneousIterableExtendsBoundTargetMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -101,13 +98,13 @@ public void shouldFailOnSuperBoundSource() { @Diagnostic( type = ErroneousIterableExtendsBoundTargetMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, - messageRegExp = "Can't generate mapping method for a wildcard extends bound result." ) + message = "Can't generate mapping method for a wildcard extends bound result." ) } ) public void shouldFailOnExtendsBoundTarget() { } - @Test + @ProcessorTest @WithClasses({ ErroneousIterableTypeVarBoundMapperOnMethod.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -115,13 +112,13 @@ public void shouldFailOnExtendsBoundTarget() { @Diagnostic(type = ErroneousIterableTypeVarBoundMapperOnMethod.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, - messageRegExp = "Can't generate mapping method for a generic type variable target." ) + message = "Can't generate mapping method for a generic type variable target." ) } ) public void shouldFailOnTypeVarSource() { } - @Test + @ProcessorTest @WithClasses({ ErroneousIterableTypeVarBoundMapperOnMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -129,18 +126,19 @@ public void shouldFailOnTypeVarSource() { @Diagnostic( type = ErroneousIterableTypeVarBoundMapperOnMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, - messageRegExp = "Can't generate mapping method for a generic type variable source." ) + 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 20309ccae4..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 @@ -24,8 +24,8 @@ 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); 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 0b964d6974..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 @@ -5,23 +5,23 @@ */ package org.mapstruct.ap.test.complex; -import static org.assertj.core.api.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, @@ -32,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 @@ -59,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 @@ -79,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 ); @@ -93,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 @@ -112,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 ); @@ -125,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 @@ -144,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(); @@ -193,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(); @@ -208,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 @@ -239,7 +245,7 @@ public void shouldMapIterableAttribute() { assertThat( dto.getPassengers().get( 1 ).getName() ).isEqualTo( "Bill" ); } - @Test + @ProcessorTest public void shouldReverseMapIterableAttribute() { //given CarDto carDto = new CarDto( @@ -247,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 @@ -261,7 +267,7 @@ public void shouldReverseMapIterableAttribute() { assertThat( car.getPassengers().get( 1 ).getName() ).isEqualTo( "Bill" ); } - @Test + @ProcessorTest public void shouldMapEnumToString() { //given Car car = new Car(); @@ -274,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/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..ae309988c8 --- /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..64b6fcbbab --- /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..44bc262435 --- /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..705421724e --- /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.PersonDto; +import org.mapstruct.ap.test.constructor.ConstructorProperties; + +/** + * @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/ContextParameterErroneousTest.java b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java index db72521e9a..46bb08cb70 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterErroneousTest.java @@ -7,21 +7,19 @@ import javax.tools.Diagnostic.Kind; -import org.junit.Test; -import org.junit.runner.RunWith; 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; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * Tests the erroneous usage of the {@link Context} annotation in the following situations: *
        - *
      • using the the same context parameter type twice in the same method + *
      • using the same context parameter type twice in the same method *
      * * @author Andreas Gudian @@ -31,17 +29,16 @@ Node.class, NodeDto.class, CycleContext.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ContextParameterErroneousTest { - @Test + @ProcessorTest @WithClasses(ErroneousNodeMapperWithNonUniqueContextTypes.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = @Diagnostic( kind = Kind.ERROR, line = 20, type = ErroneousNodeMapperWithNonUniqueContextTypes.class, - messageRegExp = "The types of @Context parameters must be unique")) + 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 index 8fc6e436e0..017183743b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/context/ContextParameterTest.java @@ -5,18 +5,16 @@ */ package org.mapstruct.ap.test.context; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; 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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * Tests the usage of the {@link Context} annotation in the following situations: @@ -44,12 +42,11 @@ CycleContextLifecycleMethods.class, FactoryContextMethods.class, SelfContainingCycleContext.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ContextParameterTest { private static final int MAGIC_NUMBER_OFFSET = 10; - @Test + @ProcessorTest public void mappingWithContextCorrectlyResolvesCycles() { Node root = buildNodes(); NodeDto rootDto = @@ -61,7 +58,7 @@ public void mappingWithContextCorrectlyResolvesCycles() { assertResult( updated ); } - @Test + @ProcessorTest public void automappingWithContextCorrectlyResolvesCycles() { Node root = buildNodes(); NodeDto rootDto = AutomappingNodeMapperWithContext.INSTANCE @@ -74,7 +71,7 @@ public void automappingWithContextCorrectlyResolvesCycles() { assertResult( updated ); } - @Test + @ProcessorTest public void automappingWithSelfContainingContextCorrectlyResolvesCycles() { Node root = buildNodes(); NodeDto rootDto = AutomappingNodeMapperWithSelfContainingContext.INSTANCE 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 index 48a82df57d..da6c87ecdf 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/context/CycleContext.java +++ b/processor/src/test/java/org/mapstruct/ap/test/context/CycleContext.java @@ -16,7 +16,7 @@ * @author Andreas Gudian */ public class CycleContext { - private Map knownInstances = new IdentityHashMap(); + private Map knownInstances = new IdentityHashMap<>(); @SuppressWarnings("unchecked") public T getMappedInstance(Object source, Class targetType) { 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 index 15a8effacb..b663b7b15f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/context/Node.java +++ b/processor/src/test/java/org/mapstruct/ap/test/context/Node.java @@ -21,8 +21,8 @@ public class Node { public Node(String name) { this.name = name; - this.children = new ArrayList(); - this.attributes = new ArrayList(); + this.children = new ArrayList<>(); + this.attributes = new ArrayList<>(); } public Node getParent() { 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 index 298b95aa2c..8b4a21640f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/context/SelfContainingCycleContext.java +++ b/processor/src/test/java/org/mapstruct/ap/test/context/SelfContainingCycleContext.java @@ -19,7 +19,7 @@ * @author Andreas Gudian */ public class SelfContainingCycleContext { - private Map knownInstances = new IdentityHashMap(); + private Map knownInstances = new IdentityHashMap<>(); @BeforeMapping @SuppressWarnings("unchecked") 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 index 08ca1ce1b9..068a5a0d95 100644 --- 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 @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.context.objectfactory; -import static org.assertj.core.api.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; /** * @@ -23,10 +21,9 @@ ValveDto.class, ContextObjectFactory.class, ContextWithObjectFactoryMapper.class}) -@RunWith(AnnotationProcessorTestRunner.class) public class ContextWithObjectFactoryTest { - @Test + @ProcessorTest public void testFactoryCalled( ) { ValveDto dto = new ValveDto(); dto.setOneWay( true ); 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 400026254a..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 @@ -5,18 +5,15 @@ */ package org.mapstruct.ap.test.conversion; -import static org.assertj.core.api.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 ); @@ -31,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 ); @@ -42,7 +39,7 @@ public void shouldHandleNulls() { assertThat( target.getZip() ).isEqualTo( "0" ); } - @Test + @ProcessorTest public void shouldApplyConversionsToMappedProperties() { Source source = new Source(); source.setQax( 42 ); @@ -55,7 +52,7 @@ public void shouldApplyConversionsToMappedProperties() { assertThat( target.getQax() ).isEqualTo( 23 ); } - @Test + @ProcessorTest public void shouldApplyConversionsForReverseMapping() { Target target = new Target(); target.setFoo( 42L ); @@ -70,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/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/SourceTargetMapper.java index ae5a4e9ce0..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 @@ -17,8 +17,8 @@ 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); 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/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/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/BigNumbersConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/bignumbers/BigNumbersConversionTest.java index 95adb3e050..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 @@ -5,35 +5,29 @@ */ package org.mapstruct.ap.test.conversion.bignumbers; -import static org.assertj.core.api.Assertions.assertThat; - import java.math.BigDecimal; import java.math.BigInteger; -import org.junit.Rule; +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 { - private final GeneratedSource generatedSource = new GeneratedSource(); - - @Rule - public GeneratedSource getGeneratedSource() { - return generatedSource; - } + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest @IssueKey("21") @WithClasses({ BigIntegerSource.class, BigIntegerTarget.class, BigIntegerMapper.class }) public void shouldApplyBigIntegerConversions() { @@ -70,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() { @@ -107,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() { @@ -146,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() { @@ -185,10 +252,86 @@ public void shouldApplyReverseBigDecimalConversions() { assertThat( source.getBigInteger() ).isEqualTo( new BigDecimal( "14" ) ); } - @Test + @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() { - getGeneratedSource().forMapper( BigIntegerMapper.class ).content().doesNotContain( "createDecimalFormat" ); + 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 index 80e0f9ffb0..57b4f7e4e4 100644 --- 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 @@ -9,12 +9,10 @@ import java.util.HashSet; import java.util.Set; -import org.junit.Test; -import org.junit.runner.RunWith; 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.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; @@ -23,14 +21,13 @@ */ @WithClasses({ CurrencyMapper.class, CurrencySource.class, CurrencyTarget.class }) @IssueKey("1355") -@RunWith(AnnotationProcessorTestRunner.class) public class CurrencyConversionTest { - @Test + @ProcessorTest public void shouldApplyCurrencyConversions() { final CurrencySource source = new CurrencySource(); source.setCurrencyA( Currency.getInstance( "USD" ) ); - Set currencies = new HashSet(); + Set currencies = new HashSet<>(); currencies.add( Currency.getInstance( "EUR" ) ); currencies.add( Currency.getInstance( "CHF" ) ); source.setUniqueCurrencies( currencies ); @@ -44,7 +41,7 @@ public void shouldApplyCurrencyConversions() { .containsExactlyInAnyOrder( "EUR", "CHF" ); } - @Test + @ProcessorTest public void shouldApplyReverseConversions() { final CurrencyTarget target = new CurrencyTarget(); target.setCurrencyA( "USD" ); 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 680303b334..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 @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.test.conversion.date; -import static org.assertj.core.api.Assertions.assertThat; - import java.sql.Time; import java.sql.Timestamp; import java.util.Arrays; @@ -14,17 +12,14 @@ 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 org.mapstruct.ap.testutil.runner.Compiler; -import org.mapstruct.ap.testutil.runner.DisabledOnCompiler; -import org.mapstruct.ap.testutil.runner.EnabledOnCompiler; + +import static org.assertj.core.api.Assertions.assertThat; /** * Tests application of format strings for conversions between strings and dates. @@ -37,80 +32,72 @@ SourceTargetMapper.class }) @IssueKey("43") -@RunWith(AnnotationProcessorTestRunner.class) +@DefaultLocale("de") +@ReadsDefaultTimeZone public class DateConversionTest { - @Before - public void setDefaultLocale() { - Locale.setDefault( Locale.GERMAN ); - } - - @Test - @DisabledOnCompiler(Compiler.JDK11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - 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" ); } - @Test - @EnabledOnCompiler(Compiler.JDK11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionsJdk11() { + @ProcessorTest + public void shouldApplyDateFormatForConversionsJdk11WithCustomLocale() { 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 ); + Target target = SourceTargetMapper.INSTANCE.sourceToTargetWithCustomLocale( source ); assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( "06.07.2013" ); + assertThat( target.getDate() ).isEqualTo( "juillet 06, 2013" ); assertThat( target.getAnotherDate() ).isEqualTo( "14.02.13, 00:00" ); } - @Test - @DisabledOnCompiler(Compiler.JDK11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - 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() + ); } - @Test - @EnabledOnCompiler(Compiler.JDK11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void shouldApplyDateFormatForConversionInReverseMappingJdk11() { + @ProcessorTest + public void shouldApplyDateFormatForConversionInReverseMappingJdk11WithCustomLocale() { Target target = new Target(); - target.setDate( "06.07.2013" ); + target.setDate( "juillet 06, 2013" ); target.setAnotherDate( "14.02.13, 8:30" ); - Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); + Source source = SourceTargetMapper.INSTANCE.targetToSourceWithCustomLocale( 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() + ); } - @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 ); @@ -119,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 ); @@ -133,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" ); @@ -141,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() ); } - @Test + @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() + ); + } + + @ProcessorTest public void shouldApplyStringConversionForReverseArrayMethod() { String[] stringDates = new String[]{ "06.07.2013", "14.02.2013", "11.04.2013" }; @@ -155,18 +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 ); @@ -174,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" }; @@ -182,15 +225,15 @@ 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") - @Test - public void shouldApplyDateToSqlConversion() throws Exception { + @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 ); @@ -211,8 +254,8 @@ public void shouldApplyDateToSqlConversion() throws Exception { } @IssueKey("858") - @Test - public void shouldApplySqlToDateConversion() throws Exception { + @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 ); 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 2ef6a7bd96..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 @@ -22,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/erroneous/InvalidDateFormatTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/erroneous/InvalidDateFormatTest.java index 669b4cfdad..81bf3eb568 100644 --- 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 @@ -5,14 +5,13 @@ */ package org.mapstruct.ap.test.conversion.erroneous; -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.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; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * @author Filip Hrisafov @@ -21,8 +20,8 @@ Source.class, Target.class }) +@WithJoda @IssueKey("725") -@RunWith(AnnotationProcessorTestRunner.class) public class InvalidDateFormatTest { @WithClasses({ @@ -33,53 +32,53 @@ public class InvalidDateFormatTest { @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 25, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\""), + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 26, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"\\."), + message = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 27, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"\\."), + message = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 28, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"\\."), + message = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 29, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"\\."), + message = "Given date format \"qwertz\" is invalid. Message: \"Unknown pattern letter: r\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 30, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\""), + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 31, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\""), + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 32, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\""), + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 33, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\""), + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern component: q\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 37, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\""), + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 40, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\""), + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\"."), @Diagnostic(type = ErroneousFormatMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 43, - messageRegExp = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\"") + message = "Given date format \"qwertz\" is invalid. Message: \"Illegal pattern character 'q'\".") }) - @Test + @ProcessorTest public void shouldFailWithInvalidDateFormats() { } } 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 573e300bfc..0059630532 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 @@ -17,23 +17,26 @@ import java.util.Date; import java.util.TimeZone; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junitpioneer.jupiter.DefaultTimeZone; +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 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" ) ) ); @@ -42,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 ) ); @@ -51,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 ) ); @@ -60,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 ) ); @@ -69,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 ) ); @@ -95,7 +98,7 @@ 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(); @@ -108,7 +111,7 @@ public void testStringToDateTime() { assertThat( src.getZonedDateTime() ).isEqualTo( sourceDateTime ); } - @Test + @ProcessorTest public void testStringToLocalDateTime() { String dateTimeAsString = "01.01.2014 00:00"; Target target = new Target(); @@ -121,7 +124,7 @@ public void testStringToLocalDateTime() { assertThat( src.getLocalDateTime() ).isEqualTo( sourceDateTime ); } - @Test + @ProcessorTest public void testStringToLocalDate() { String dateTimeAsString = "01.01.2014"; Target target = new Target(); @@ -134,7 +137,7 @@ public void testStringToLocalDate() { assertThat( src.getLocalDate() ).isEqualTo( sourceDate ); } - @Test + @ProcessorTest public void testStringToLocalTime() { String dateTimeAsString = "00:00"; Target target = new Target(); @@ -147,7 +150,7 @@ public void testStringToLocalTime() { assertThat( src.getLocalTime() ).isEqualTo( sourceTime ); } - @Test + @ProcessorTest public void testTargetToSourceNullMapping() { Target target = new Target(); Source src = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -159,7 +162,7 @@ public void testTargetToSourceNullMapping() { assertThat( src.getLocalTime() ).isNull(); } - @Test + @ProcessorTest public void testTargetToSourceMappingForStrings() { Target target = new Target(); @@ -184,7 +187,7 @@ public void testTargetToSourceMappingForStrings() { 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" ) ); @@ -209,9 +212,9 @@ 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( @@ -234,7 +237,7 @@ public void testZonedDateTimeToDateMapping() { assertThat( source.getForDateConversionWithZonedDateTime() ).isEqualTo( dateTime ); } - @Test + @ProcessorTest public void testInstantToDateMapping() { Instant instant = Instant.ofEpochMilli( 1539366615000L ); @@ -249,9 +252,24 @@ public void testInstantToDateMapping() { 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( "Australia/Melbourne" ) ); Source source = new Source(); LocalDateTime dateTime = LocalDateTime.of( 2014, 1, 1, 0, 0 ); @@ -275,9 +293,9 @@ public void testLocalDateTimeToDateMapping() { assertThat( source.getForDateConversionWithLocalDateTime() ).isEqualTo( dateTime ); } - @Test + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") public void testLocalDateToDateMapping() { - TimeZone.setDefault( TimeZone.getTimeZone( "Australia/Melbourne" ) ); Source source = new Source(); LocalDate localDate = LocalDate.of( 2016, 3, 1 ); @@ -299,9 +317,9 @@ public void testLocalDateToDateMapping() { assertThat( source.getForDateConversionWithLocalDate() ).isEqualTo( localDate ); } - @Test + @ProcessorTest + @DefaultTimeZone("Australia/Melbourne") public void testLocalDateToSqlDateMapping() { - TimeZone.setDefault( TimeZone.getTimeZone( "Australia/Melbourne" ) ); Source source = new Source(); LocalDate localDate = LocalDate.of( 2016, 3, 1 ); @@ -323,7 +341,7 @@ public void testLocalDateToSqlDateMapping() { assertThat( source.getForSqlDateConversionWithLocalDate() ).isEqualTo( localDate ); } - @Test + @ProcessorTest public void testInstantToStringMapping() { Source source = new Source(); source.setForInstantConversionWithString( Instant.ofEpochSecond( 42L ) ); @@ -333,7 +351,7 @@ public void testInstantToStringMapping() { assertThat( periodString ).isEqualTo( "1970-01-01T00:00:42Z" ); } - @Test + @ProcessorTest public void testInstantToStringNullMapping() { Source source = new Source(); source.setForInstantConversionWithString( null ); @@ -343,7 +361,7 @@ public void testInstantToStringNullMapping() { assertThat( periodString ).isNull(); } - @Test + @ProcessorTest public void testStringToInstantMapping() { Target target = new Target(); target.setForInstantConversionWithString( "1970-01-01T00:00:00.000Z" ); @@ -353,7 +371,7 @@ public void testStringToInstantMapping() { assertThat( instant ).isEqualTo( Instant.EPOCH ); } - @Test + @ProcessorTest public void testStringToInstantNullMapping() { Target target = new Target(); target.setForInstantConversionWithString( null ); @@ -363,7 +381,7 @@ public void testStringToInstantNullMapping() { assertThat( instant ).isNull(); } - @Test + @ProcessorTest public void testPeriodToStringMapping() { Source source = new Source(); source.setForPeriodConversionWithString( Period.ofDays( 42 ) ); @@ -373,7 +391,7 @@ public void testPeriodToStringMapping() { assertThat( periodString ).isEqualTo( "P42D" ); } - @Test + @ProcessorTest public void testPeriodToStringNullMapping() { Source source = new Source(); source.setForPeriodConversionWithString( null ); @@ -383,7 +401,7 @@ public void testPeriodToStringNullMapping() { assertThat( periodString ).isNull(); } - @Test + @ProcessorTest public void testStringToPeriodMapping() { Target target = new Target(); target.setForPeriodConversionWithString( "P1Y2M3D" ); @@ -393,7 +411,7 @@ public void testStringToPeriodMapping() { assertThat( period ).isEqualTo( Period.of( 1, 2, 3 ) ); } - @Test + @ProcessorTest public void testStringToPeriodNullMapping() { Target target = new Target(); target.setForPeriodConversionWithString( null ); @@ -403,7 +421,7 @@ public void testStringToPeriodNullMapping() { assertThat( period ).isNull(); } - @Test + @ProcessorTest public void testDurationToStringMapping() { Source source = new Source(); source.setForDurationConversionWithString( Duration.ofMinutes( 42L ) ); @@ -413,7 +431,7 @@ public void testDurationToStringMapping() { assertThat( durationString ).isEqualTo( "PT42M" ); } - @Test + @ProcessorTest public void testDurationToStringNullMapping() { Source source = new Source(); source.setForDurationConversionWithString( null ); @@ -423,7 +441,7 @@ public void testDurationToStringNullMapping() { assertThat( durationString ).isNull(); } - @Test + @ProcessorTest public void testStringToDurationMapping() { Target target = new Target(); target.setForDurationConversionWithString( "PT20.345S" ); @@ -433,7 +451,7 @@ public void testStringToDurationMapping() { assertThat( duration ).isEqualTo( Duration.ofSeconds( 20L, 345000000L ) ); } - @Test + @ProcessorTest public void testStringToDurationNullMapping() { Target target = new Target(); target.setForDurationConversionWithString( null ); 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 index 2e5c7b63e7..935e12d55d 100644 --- 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 @@ -5,32 +5,28 @@ */ package org.mapstruct.ap.test.conversion.java8time; -import static org.assertj.core.api.Assertions.assertThat; - 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.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 org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * @author Andreas Gudian */ @IssueKey("580") @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class LocalDateToXMLGregorianCalendarConversionTest { - @Test + @ProcessorTest public void shouldNullCheckOnBuiltinAndConversion() { Target target = SourceTargetMapper.INSTANCE.toTarget( new Source() ); @@ -43,7 +39,7 @@ public void shouldNullCheckOnBuiltinAndConversion() { assertThat( source.getDate() ).isNull(); } - @Test + @ProcessorTest public void shouldMapCorrectlyOnBuiltinAndConversion() throws Exception { XMLGregorianCalendar calendarDate = DatatypeFactory.newInstance().newXMLGregorianCalendarDate( 2007, 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 91298617cd..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 @@ -38,6 +38,8 @@ public class Source { private Instant forDateConversionWithInstant; + private LocalDate forLocalDateTimeConversionWithLocalDate; + private Instant forInstantConversionWithString; private Period forPeriodConversionWithString; @@ -124,6 +126,14 @@ 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; } 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 37fc7d6cbe..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 @@ -26,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 188a6d0e20..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 @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.conversion.java8time; +import java.time.LocalDateTime; import java.util.Calendar; import java.util.Date; @@ -33,6 +34,8 @@ public class Target { private Date forDateConversionWithInstant; + private LocalDateTime forLocalDateTimeConversionWithLocalDate; + private String forInstantConversionWithString; private String forPeriodConversionWithString; @@ -119,6 +122,14 @@ 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; } 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 index 23fc7a5ed4..081fc1edb9 100644 --- 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 @@ -7,7 +7,6 @@ import javax.xml.datatype.XMLGregorianCalendar; - /** * @author Andreas Gudian */ 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 30c96395b7..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 @@ -5,10 +5,7 @@ */ package org.mapstruct.ap.test.conversion.jodatime; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Calendar; -import java.util.Locale; import java.util.TimeZone; import org.joda.time.DateTime; @@ -16,32 +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.runner.Compiler; -import org.mapstruct.ap.testutil.runner.DisabledOnCompiler; -import org.mapstruct.ap.testutil.runner.EnabledOnCompiler; +import org.mapstruct.ap.testutil.WithJoda; + +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 ) ); @@ -50,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 ) ); @@ -59,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 ) ); @@ -68,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 ) ); @@ -77,9 +68,7 @@ public void testLocalTimeToString() { assertThat( target.getLocalTime() ).isEqualTo( "00:00" ); } - @Test - @DisabledOnCompiler(Compiler.JDK11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ + @ProcessorTest public void testSourceToTargetMappingForStrings() { Source src = new Source(); src.setLocalTime( new LocalTime( 0, 0 ) ); @@ -99,41 +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.getLocalDate() ).isEqualTo( "1. Januar 2014" ); - assertThat( target.getLocalTime() ).isEqualTo( "00:00:00" ); - } - - @Test - @EnabledOnCompiler(Compiler.JDK11) - // See https://bugs.openjdk.java.net/browse/JDK-8211262, there is a difference in the default formats on Java 9+ - public void testSourceToTargetMappingForStringsJdk11() { - Source src = new Source(); - src.setLocalTime( new LocalTime( 0, 0 ) ); - src.setLocalDate( new LocalDate( 2014, 1, 1 ) ); - src.setLocalDateTime( new LocalDateTime( 2014, 1, 1, 0, 0 ) ); - src.setDateTime( new DateTime( 2014, 1, 1, 0, 0, 0, DateTimeZone.UTC ) ); - - // with given format - Target target = SourceTargetMapper.INSTANCE.sourceToTarget( src ); - - assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).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 = SourceTargetMapper.INSTANCE.sourceToTargetDefaultMapping( src ); - assertThat( target ).isNotNull(); - assertThat( target.getDateTime() ).isEqualTo( "1. Januar 2014 um 00:00:00 UTC" ); - assertThat( target.getLocalDateTime() ).isEqualTo( "1. Januar 2014 um 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(); @@ -146,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(); @@ -159,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(); @@ -172,7 +133,7 @@ public void testStringToLocalDate() { assertThat( src.getLocalDate() ).isEqualTo( sourceDate ); } - @Test + @ProcessorTest public void testStringToLocalTime() { String dateTimeAsString = "00:00"; Target target = new Target(); @@ -185,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 ); @@ -197,7 +158,7 @@ public void testTargetToSourceNullMapping() { assertThat( src.getLocalTime() ).isNull(); } - @Test + @ProcessorTest public void testTargetToSourceMappingForStrings() { Target target = new Target(); @@ -214,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 ); @@ -230,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/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 index 5a96d6deb2..3bc144511e 100644 --- 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 @@ -10,6 +10,7 @@ public class CutleryInventoryDto { private short numberOfKnifes; private int numberOfForks; private byte numberOfSpoons; + private int drawerId; private float approximateKnifeLength; @@ -44,4 +45,12 @@ public float getApproximateKnifeLength() { 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 index 9575ed99dc..755dbc3637 100644 --- 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 @@ -10,6 +10,7 @@ public class CutleryInventoryEntity { private int numberOfKnifes; private Long numberOfForks; private short numberOfSpoons; + private String drawerId; private double approximateKnifeLength; @@ -44,4 +45,12 @@ public double getApproximateKnifeLength() { 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/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/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/LossyConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/LossyConversionTest.java index de1921ea5d..604ff9bffa 100644 --- 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 @@ -5,14 +5,12 @@ */ package org.mapstruct.ap.test.conversion.lossy; -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; import static org.assertj.core.api.Assertions.withinPercentage; @@ -22,9 +20,9 @@ * * @author Sjaak Derksen */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ OversizedKitchenDrawerDto.class, + OversizedKitchenDrawerOptionalDto.class, RegularKitchenDrawerEntity.class, VerySpecialNumber.class, VerySpecialNumberMapper.class, @@ -35,7 +33,7 @@ @IssueKey("5") public class LossyConversionTest { - @Test + @ProcessorTest public void testNoErrorCase() { CutleryInventoryDto dto = new CutleryInventoryDto(); @@ -43,116 +41,223 @@ public void testNoErrorCase() { 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" ); } - @Test + @ProcessorTest @WithClasses(ErroneousKitchenDrawerMapper1.class) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = ErroneousKitchenDrawerMapper1.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 24, - messageRegExp = "Can't map property \"long numberOfForks\". It has a possibly lossy conversion from " + message = "Can't map property \"long numberOfForks\". It has a possibly lossy conversion from " + "long to int.") }) - public void testConversionFromlongToint() { + public void testConversionFromLongToInt() { } - @Test + @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, - messageRegExp = "property \"java.math.BigInteger numberOfKnifes\" has a possibly lossy conversion " - + "from java.math.BigInteger to java.lang.Integer.") + message = "property \"BigInteger numberOfKnifes\" has a possibly lossy conversion " + + "from BigInteger to Integer.") }) public void testConversionFromBigIntegerToInteger() { } - @Test + @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, - messageRegExp = "org.mapstruct.ap.test.conversion.lossy.VerySpecialNumber numberOfSpoons\". It has " - + "a possibly lossy conversion from java.math.BigInteger to java.lang.Long") + message = "Can't map property \"VerySpecialNumber numberOfSpoons\". " + + "It has a possibly lossy conversion from BigInteger to Long.") }) public void test2StepConversionFromBigIntegerToLong() { } - @Test + @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, - messageRegExp = "Can't map property \"java.lang.Double depth\". It has a possibly lossy conversion " - + "from java.lang.Double to float.") + message = + "Can't map property \"Double depth\". It has a possibly lossy conversion from Double to float.") }) - public void testConversionFromDoubleTofloat() { + public void testConversionFromDoubleToFloat() { } - @Test + @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, - messageRegExp = "\"java.math.BigDecimal length\". It has a possibly lossy conversion from " - + "java.math.BigDecimal to java.lang.Float.") + message = + "Can't map property \"BigDecimal length\". It has a possibly lossy conversion from BigDecimal to Float.") }) public void testConversionFromBigDecimalToFloat() { } - @Test + @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, - messageRegExp = "property \"double height\" has a possibly lossy conversion from double to float.") + 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 test2StepConversionFromdoubleTofloat() { + public void test2StepConversionFromOptionalDoubleToFloat() { } - @Test + @ProcessorTest @WithClasses(ListMapper.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diagnostics = { @Diagnostic(type = ListMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 21, - messageRegExp = "collection element has a possibly lossy conversion from java.math.BigDecimal to " - + "java.math.BigInteger") + message = "collection element has a possibly lossy conversion from BigDecimal to BigInteger.") }) public void testListElementConversion() { } - @Test + @ProcessorTest @WithClasses(MapMapper.class) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diagnostics = { @Diagnostic(type = MapMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 19, - messageRegExp = "map key has a possibly lossy conversion from java.lang.Long to java.lang.Integer."), + message = "map key has a possibly lossy conversion from Long to Integer."), @Diagnostic(type = MapMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 19, - messageRegExp = "map value has a possibly lossy conversion from java.lang.Double to java.lang.Float.") + 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/OversizedKitchenDrawerDto.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/OversizedKitchenDrawerDto.java index 110bb2f07c..c42b24cc57 100644 --- 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 @@ -21,6 +21,7 @@ public class OversizedKitchenDrawerDto { private Double depth; private BigDecimal length; private double height; + private String drawerId; public long getNumberOfForks() { return numberOfForks; @@ -70,4 +71,11 @@ 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 index d49a89052d..e3ae10bc1f 100644 --- 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 @@ -17,6 +17,7 @@ public class RegularKitchenDrawerEntity { private float depth; private Float length; private VerySpecialNumber height; + private int drawerId; public int getNumberOfForks() { return numberOfForks; @@ -66,4 +67,11 @@ 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/VerySpecialNumberMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/lossy/VerySpecialNumberMapper.java index 29531bea0b..00b543c728 100644 --- 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 @@ -6,18 +6,23 @@ 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 ) { + VerySpecialNumber fromFloat(float f) { return new VerySpecialNumber(); } - BigInteger toBigInteger( VerySpecialNumber v ) { + 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 9e10dbf959..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 @@ -5,23 +5,20 @@ */ package org.mapstruct.ap.test.conversion.nativetypes; -import static org.assertj.core.api.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; @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 ); @@ -34,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 ); @@ -47,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/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/CharConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/CharConversionTest.java index f2cc7e3417..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 @@ -5,23 +5,20 @@ */ package org.mapstruct.ap.test.conversion.nativetypes; -import static org.assertj.core.api.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; @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' ); @@ -32,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 ); @@ -43,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/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/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/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/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/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/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/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/NumberConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/nativetypes/NumberConversionTest.java index b0dedb61c1..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 @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.conversion.nativetypes; -import static org.assertj.core.api.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; @WithClasses({ ByteSource.class, @@ -40,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 ); @@ -76,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 ); @@ -109,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 ); @@ -142,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 ); @@ -175,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 ); @@ -208,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 ); @@ -241,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 ); @@ -274,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 ); @@ -307,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 ); @@ -340,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 ); @@ -373,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 ); @@ -406,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 ); @@ -439,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/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/numbers/NumberFormatConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/NumberFormatConversionTest.java index ed358ff6b8..39b4e98319 100644 --- 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 @@ -5,20 +5,18 @@ */ package org.mapstruct.ap.test.conversion.numbers; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; - import java.math.BigDecimal; import java.math.BigInteger; -import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.Locale; 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; @@ -27,15 +25,14 @@ Target.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) +@DefaultLocale("en") public class NumberFormatConversionTest { - @Before - public void setDefaultLocale() { - Locale.setDefault( Locale.ENGLISH ); - } + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( SourceTargetMapper.class ); - @Test + @ProcessorTest public void shouldApplyStringConversions() { Source source = new Source(); source.setI( 1 ); @@ -76,7 +73,48 @@ public void shouldApplyStringConversions() { assertThat( target.getBigInteger1() ).isEqualTo( "1.23456789E12" ); } - @Test + @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" ); @@ -117,23 +155,79 @@ public void shouldApplyReverseStringConversions() { assertThat( source.getBigInteger1() ).isEqualTo( new BigInteger( "1234567890000" ) ); } - @Test + @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( Arrays.asList( 2f ) ); + List target = SourceTargetMapper.INSTANCE.sourceToTarget( List.of( 2f ) ); assertThat( target ).hasSize( 1 ); - assertThat( target ).isEqualTo( Arrays.asList( "2.00" ) ); + assertThat( target ).isEqualTo( List.of( "2.00" ) ); List source = SourceTargetMapper.INSTANCE.targetToSource( target ); assertThat( source ).hasSize( 1 ); - assertThat( source ).isEqualTo( Arrays.asList( 2.00f ) ); + assertThat( source ).isEqualTo( List.of( 2.00f ) ); } - @Test + @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(); + Map source1 = new HashMap<>(); source1.put( 1.0001f, 2.01f ); Map target = SourceTargetMapper.INSTANCE.sourceToTarget( source1 ); @@ -145,4 +239,20 @@ public void shouldApplyStringConversionsToMaps() { 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/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/numbers/SourceTargetMapper.java index 537452fca2..b82a7a6199 100644 --- 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 @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.conversion.numbers; +import java.math.BigDecimal; import java.util.List; import java.util.Map; import org.mapstruct.InheritInverseConfiguration; @@ -41,21 +42,55 @@ public interface SourceTargetMapper { } ) Target sourceToTarget(Source source); - @InheritInverseConfiguration + @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 + @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); - @InheritInverseConfiguration + @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/precedence/ConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/precedence/ConversionTest.java index 5a3a968c1e..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 @@ -5,18 +5,15 @@ */ package org.mapstruct.ap.test.conversion.precedence; -import static org.assertj.core.api.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/string/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/Source.java index ae9f226351..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 @@ -23,6 +23,7 @@ public class Source { private Boolean boolBool; private char c; private Character cc; + private StringBuilder sb; private Object object; public byte getB() { @@ -160,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/StringConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/string/StringConversionTest.java index 8b7fe0f058..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 @@ -5,12 +5,10 @@ */ package org.mapstruct.ap.test.conversion.string; -import org.junit.Rule; -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; @@ -20,16 +18,15 @@ Target.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class StringConversionTest { private static final String STRING_CONSTANT = "String constant"; - @Rule - public final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( SourceTargetMapper.class ); - @Test + @ProcessorTest public void shouldApplyStringConversions() { Source source = new Source(); source.setB( (byte) 1 ); @@ -48,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 ); @@ -68,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" ); } - @Test + @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(); + } + + @ProcessorTest public void shouldApplyReverseStringConversions() { Target target = new Target(); target.setB( "1" ); @@ -89,6 +115,7 @@ public void shouldApplyReverseStringConversions() { target.setBoolBool( "true" ); target.setC( "G" ); target.setCc( "H" ); + target.setSb( "SB" ); Source source = SourceTargetMapper.INSTANCE.targetToSource( target ); @@ -109,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 db6c3553c2..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 @@ -23,6 +23,7 @@ public class Target { private String boolBool; private String c; private String cc; + private String sb; private String object; public String getB() { @@ -160,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/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..bd1eed1480 --- /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 org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import java.net.MalformedURLException; +import java.net.URL; + +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( new URL("https://mapstruct.org/") ); + + 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( new URL( target.getURL() ) ); + } + + @ProcessorTest + public void shouldHandleInvalidURLString() { + Target target = new Target(); + target.setInvalidURL( "XXXXXXXXX" ); + + assertThatThrownBy( () -> URLMapper.INSTANCE.targetToSource( target ) ) + .isInstanceOf( RuntimeException.class ) + .getRootCause().isInstanceOf( MalformedURLException.class ); + } + + @ProcessorTest + public void shouldHandleInvalidURLStringWithMalformedURLException() { + Target target = new Target(); + target.setInvalidURL( "XXXXXXXXX" ); + + 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..18fc5bc7de --- /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 org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.net.MalformedURLException; + +@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/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/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 08e71f8369..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 @@ -5,20 +5,17 @@ */ package org.mapstruct.ap.test.decorator; -import static org.assertj.core.api.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. @@ -32,10 +29,9 @@ AddressDto.class }) @IssueKey("163") -@RunWith(AnnotationProcessorTestRunner.class) public class DecoratorTest { - @Test + @ProcessorTest @WithClasses({ PersonMapper.class, PersonMapperDecorator.class @@ -43,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 @@ -56,7 +52,7 @@ public void shouldInvokeDecoratorMethods() { assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest @WithClasses({ PersonMapper.class, PersonMapperDecorator.class @@ -73,7 +69,7 @@ public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { assertThat( addressDto.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest @WithClasses({ PersonMapper.class, PersonMapperDecorator.class @@ -91,7 +87,7 @@ public void shouldDelegateNonDecoratedVoidMethodsToDefaultImplementation() { assertThat( address.getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest @WithClasses({ AnotherPersonMapper.class, AnotherPersonMapperDecorator.class @@ -99,7 +95,7 @@ public void shouldDelegateNonDecoratedVoidMethodsToDefaultImplementation() { 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 @@ -112,7 +108,7 @@ public void shouldApplyDecoratorWithDefaultConstructor() { assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest @WithClasses({ YetAnotherPersonMapper.class, YetAnotherPersonMapperDecorator.class @@ -120,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" ) ); @@ -135,7 +131,7 @@ public void shouldApplyDelegateToClassBasedMapper() { } @IssueKey("173") - @Test + @ProcessorTest @WithClasses({ Person2Mapper.class, Person2MapperDecorator.class, @@ -150,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" ) ); @@ -171,7 +167,7 @@ public void shouldApplyCustomMappers() { assertThat( personDto.getSportsClub().getName() ).isEqualTo( "SC Duckburg" ); } - @Test + @ProcessorTest @WithClasses({ ErroneousPersonMapper.class, ErroneousPersonMapperDecorator.class @@ -182,7 +178,7 @@ public void shouldApplyCustomMappers() { @Diagnostic(type = ErroneousPersonMapper.class, kind = Kind.ERROR, line = 14, - messageRegExp = "Specified decorator type is no subtype of the annotated mapper type") + 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/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/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..e336f913b6 --- /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..ce9779f368 --- /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..f16a9f8c1e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/decorator/jakarta/jsr330/PersonMapperDecorator.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.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 2e95f3d2ee..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 @@ -5,31 +5,18 @@ */ package org.mapstruct.ap.test.decorator.jsr330; -import static java.lang.System.lineSeparator; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Calendar; - -import javax.inject.Inject; -import javax.inject.Named; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -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.mapstruct.ap.testutil.WithJavaxInject; 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; /** * Test for the application of decorators using component model jsr330. @@ -45,62 +32,14 @@ PersonMapperDecorator.class }) @IssueKey("592") -@RunWith(AnnotationProcessorTestRunner.class) -@ComponentScan(basePackageClasses = Jsr330DecoratorTest.class) -@Configuration +@WithJavaxInject public class Jsr330DecoratorTest { - private final GeneratedSource generatedSource = new GeneratedSource(); - - @Inject - @Named - private PersonMapper personMapper; - private ConfigurableApplicationContext context; - - @Rule - public GeneratedSource getGeneratedSource() { - return generatedSource; - } - - @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() { - Calendar birthday = Calendar.getInstance(); - birthday.set( 1928, 4, 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" ); - } - - @Test - 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" ); - } + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); @IssueKey("664") - @Test + @ProcessorTest public void hasSingletonAnnotation() { // check the decorator generatedSource.forMapper( PersonMapper.class ).content() 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 b37557bae1..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 @@ -8,12 +8,13 @@ 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/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/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 index e1e57eb6f3..4ada1124c0 100644 --- 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 @@ -9,12 +9,13 @@ 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 = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.CONSTRUCTOR) @DecoratedWith(PersonMapperDecorator.class) public interface PersonMapper { 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 index 2ed238490a..6d18555372 100644 --- 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 @@ -5,27 +5,26 @@ */ package org.mapstruct.ap.test.decorator.spring.constructor; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Calendar; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +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.runner.AnnotationProcessorTestRunner; +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. * @@ -40,33 +39,33 @@ PersonMapperDecorator.class }) @IssueKey("592") -@RunWith(AnnotationProcessorTestRunner.class) @ComponentScan(basePackageClasses = SpringDecoratorTest.class) @Configuration +@WithSpring public class SpringDecoratorTest { @Autowired private PersonMapper personMapper; private ConfigurableApplicationContext context; - @Before + @BeforeEach public void springUp() { context = new AnnotationConfigApplicationContext( getClass() ); context.getAutowireCapableBeanFactory().autowireBean( this ); } - @After + @AfterEach public void springDown() { if ( context != null ) { context.close(); } } - @Test + @ProcessorTest 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 @@ -79,7 +78,7 @@ public void shouldInvokeDecoratorMethods() { assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { //given Address address = new Address( "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 index fc03b6ce1a..5cbe867e7a 100644 --- 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 @@ -9,12 +9,13 @@ 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 = "spring", injectionStrategy = InjectionStrategy.FIELD) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.FIELD) @DecoratedWith(PersonMapperDecorator.class) public interface PersonMapper { 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 index 2890a4a9e4..ac0af86381 100644 --- 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 @@ -5,27 +5,26 @@ */ package org.mapstruct.ap.test.decorator.spring.field; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Calendar; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; +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.runner.AnnotationProcessorTestRunner; +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. * @@ -39,8 +38,8 @@ PersonMapper.class, PersonMapperDecorator.class }) +@WithSpring @IssueKey("592") -@RunWith(AnnotationProcessorTestRunner.class) @ComponentScan(basePackageClasses = SpringDecoratorTest.class) @Configuration public class SpringDecoratorTest { @@ -49,24 +48,24 @@ public class SpringDecoratorTest { private PersonMapper personMapper; private ConfigurableApplicationContext context; - @Before + @BeforeEach public void springUp() { context = new AnnotationConfigApplicationContext( getClass() ); context.getAutowireCapableBeanFactory().autowireBean( this ); } - @After + @AfterEach public void springDown() { if ( context != null ) { context.close(); } } - @Test + @ProcessorTest 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 @@ -79,7 +78,7 @@ public void shouldInvokeDecoratorMethods() { assertThat( personDto.getAddress().getAddressLine() ).isEqualTo( "42 Ocean View Drive" ); } - @Test + @ProcessorTest public void shouldDelegateNonDecoratedMethodsToDefaultImplementation() { //given Address address = new Address( "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/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 9107b4eb2a..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 @@ -5,33 +5,28 @@ */ package org.mapstruct.ap.test.defaultvalue; -import static org.assertj.core.api.Assertions.assertThat; - -import java.text.ParseException; - -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; - -@IssueKey( "600" ) -@RunWith( AnnotationProcessorTestRunner.class ) -@WithClasses( { - CountryEntity.class, - CountryDts.class, - Continent.class -} ) + +import static org.assertj.core.api.Assertions.assertThat; + +@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: *
        @@ -57,11 +52,11 @@ public void shouldDefaultValueAndUseConstantExpression() { assertThat( countryDts.getContinent() ).isEqualTo( Continent.EUROPE ); } - @Test - @WithClasses( { - Region.class, - CountryMapper.class - } ) + @ProcessorTest + @WithClasses({ + Region.class, + CountryMapper.class + }) public void shouldIgnoreDefaultValue() { CountryEntity countryEntity = new CountryEntity(); countryEntity.setCode( "US" ); @@ -78,11 +73,11 @@ public void shouldIgnoreDefaultValue() { assertThat( countryDts.getContinent() ).isEqualTo( Continent.NORTH_AMERICA ); } - @Test - @WithClasses( { - Region.class, - CountryMapper.class - } ) + @ProcessorTest + @WithClasses({ + Region.class, + CountryMapper.class + }) public void shouldHandleUpdateMethodsFromDtsToEntity() { CountryEntity countryEntity = new CountryEntity(); CountryDts countryDts = new CountryDts(); @@ -96,11 +91,11 @@ public void shouldHandleUpdateMethodsFromDtsToEntity() { assertThat( countryEntity.getContinent() ).isEqualTo( Continent.EUROPE ); } - @Test - @WithClasses( { - Region.class, - CountryMapper.class - } ) + @ProcessorTest + @WithClasses({ + Region.class, + CountryMapper.class + }) public void shouldHandleUpdateMethodsFromEntityToEntity() { CountryEntity source = new CountryEntity(); CountryEntity target = new CountryEntity(); @@ -115,48 +110,84 @@ public void shouldHandleUpdateMethodsFromEntityToEntity() { assertThat( target.getContinent() ).isEqualTo( Continent.EUROPE ); } - @Test - @WithClasses( { - ErroneousMapper.class, - Region.class, - } ) + @ProcessorTest + @WithClasses({ + ErroneousMapper.class, + Region.class, + }) @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic( type = ErroneousMapper.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 18, - messageRegExp = "Constant and default value are both defined in @Mapping," - + " either define a defaultValue or a constant." ), - @Diagnostic(type = ErroneousMapper.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 20, - messageRegExp = "Can't map property \".*Region region\" to \".*String region\"\\. Consider") - } + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 18, + message = "Constant and default value are both defined in @Mapping, either define a defaultValue or a" + + " constant."), + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"Region region\" to \"String region\". " + + "Consider to declare/implement a mapping method: \"String map(Region value)\".") + } ) - public void errorOnDefaultValueAndConstant() throws ParseException { + public void errorOnDefaultValueAndConstant() { + } + + @ProcessorTest + @WithClasses({ + ErroneousMapper2.class, + Region.class, + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMapper2.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 18, + message = "Expression and default value are both defined in @Mapping, either define a defaultValue or" + + " an expression."), + @Diagnostic(type = ErroneousMapper2.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"Region region\" to \"String region\". " + + "Consider to declare/implement a mapping method: \"String map(Region value)\".") + } + ) + public void errorOnDefaultValueAndExpression() { + } + + @ProcessorTest + @IssueKey("2214") + @WithClasses({ + CountryMapperMultipleSources.class, + Region.class, + }) + public void shouldBeAbleToDetermineDefaultValueBasedOnOnlyTargetType() { + CountryEntity entity = new CountryEntity(); + CountryDts target = CountryMapperMultipleSources.INSTANCE.map( entity, "ZH" ); + + assertThat( target ).isNotNull(); + assertThat( target.getCode() ).isEqualTo( "CH" ); } - @Test - @WithClasses( { - ErroneousMapper2.class, - Region.class, - } ) + @ProcessorTest + @IssueKey("2220") + @WithClasses({ + ErroneousMissingSourceMapper.class, + Region.class, + }) @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic( type = ErroneousMapper2.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 18, - messageRegExp = "Expression and default value are both defined in @Mapping," - + " either define a defaultValue or an expression." ), - @Diagnostic(type = ErroneousMapper2.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 20, - messageRegExp = "Can't map property \".*Region region\" to \".*String region\"\\. Consider") - } + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = ErroneousMissingSourceMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "The type of parameter \"tenant\" has no property named \"type\"." + + " Please define the source property explicitly."), + } ) - public void errorOnDefaultValueAndExpression() throws ParseException { + public void errorWhenOnlyTargetDefinedAndSourceDoesNotHaveProperty() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMissingSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMissingSourceMapper.java new file mode 100644 index 0000000000..54fd3cc645 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/defaultvalue/ErroneousMissingSourceMapper.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.defaultvalue; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ErroneousMissingSourceMapper { + + @Mapping(target = "type", defaultValue = "STANDARD") + Tenant map(TenantDto tenant); + + class Tenant { + + private final String id; + private final String type; + + public Tenant(String id, String type) { + this.id = id; + this.type = type; + } + } + + class TenantDto { + + private final String id; + + public TenantDto(String id) { + this.id = id; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/dependency/GraphAnalyzerTest.java b/processor/src/test/java/org/mapstruct/ap/test/dependency/GraphAnalyzerTest.java index 9a6a0a0515..cbb0959b89 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/dependency/GraphAnalyzerTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/dependency/GraphAnalyzerTest.java @@ -5,16 +5,16 @@ */ package org.mapstruct.ap.test.dependency; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.HashSet; import java.util.List; import java.util.Set; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer; import org.mapstruct.ap.internal.util.Strings; +import static org.assertj.core.api.Assertions.assertThat; + /** * Unit test for {@link GraphAnalyzer}. * @@ -165,7 +165,7 @@ public void eightNodesWithoutCycle() { } private Set asStrings(Set> cycles) { - Set asStrings = new HashSet(); + Set asStrings = new HashSet<>(); for ( List cycle : cycles ) { asStrings.add( asString( cycle ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/dependency/OrderingTest.java b/processor/src/test/java/org/mapstruct/ap/test/dependency/OrderingTest.java index fd091d21a7..cdf4cf6e97 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/dependency/OrderingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/dependency/OrderingTest.java @@ -5,17 +5,15 @@ */ package org.mapstruct.ap.test.dependency; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.Mapping; 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 ordering mapped attributes by means of {@link Mapping#dependsOn()}. @@ -23,10 +21,9 @@ * @author Gunnar Morling */ @WithClasses({ Person.class, PersonDto.class, Address.class, AddressDto.class, AddressMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class OrderingTest { - @Test + @ProcessorTest @IssueKey("304") public void shouldApplyChainOfDependencies() { Address source = new Address(); @@ -40,7 +37,7 @@ public void shouldApplyChainOfDependencies() { assertThat( target.getFullName() ).isEqualTo( "Bob J. McRobb" ); } - @Test + @ProcessorTest @IssueKey("304") public void shouldApplySeveralDependenciesConfiguredForOneProperty() { Person source = new Person(); @@ -54,7 +51,7 @@ public void shouldApplySeveralDependenciesConfiguredForOneProperty() { assertThat( target.getFullName() ).isEqualTo( "Bob J. McRobb" ); } - @Test + @ProcessorTest @IssueKey("304") @WithClasses(ErroneousAddressMapperWithCyclicDependency.class) @ExpectedCompilationOutcome( @@ -63,15 +60,15 @@ public void shouldApplySeveralDependenciesConfiguredForOneProperty() { @Diagnostic(type = ErroneousAddressMapperWithCyclicDependency.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 24, - messageRegExp = "Cycle\\(s\\) between properties given via dependsOn\\(\\): firstName -> lastName -> " - + "middleName -> firstName" + message = "Cycle(s) between properties given via dependsOn(): lastName -> middleName -> " + + "firstName -> lastName." ) } ) public void shouldReportErrorIfDependenciesContainCycle() { } - @Test + @ProcessorTest @IssueKey("304") @WithClasses(ErroneousAddressMapperWithUnknownPropertyInDependsOn.class) @ExpectedCompilationOutcome( @@ -80,10 +77,10 @@ public void shouldReportErrorIfDependenciesContainCycle() { @Diagnostic(type = ErroneousAddressMapperWithUnknownPropertyInDependsOn.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 19, - messageRegExp = "\"doesnotexist\" is no property of the method return type" + message = "\"doesnotexist\" is no property of the method return type." ) } ) - public void shouldReportErrorIfPropertiyGivenInDependsOnDoesNotExist() { + public void shouldReportErrorIfPropertyGivenInDependsOnDoesNotExist() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java index 9498b832f7..8dd010cae3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationClassNameMapper.java @@ -16,5 +16,5 @@ public abstract class AbstractDestinationClassNameMapper { public static final AbstractDestinationClassNameMapper INSTANCE = Mappers.getMapper( AbstractDestinationClassNameMapper.class ); - public abstract String intToString(Integer source); + public abstract Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java index b59d5cd21b..e26a6bbe71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/AbstractDestinationPackageNameMapper.java @@ -16,5 +16,5 @@ public abstract class AbstractDestinationPackageNameMapper { public static final AbstractDestinationPackageNameMapper INSTANCE = Mappers.getMapper( AbstractDestinationPackageNameMapper.class ); - public abstract String intToString(Integer source); + public abstract Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java index a331096a63..99bd8915a5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapper.java @@ -15,5 +15,5 @@ public interface DestinationClassNameMapper { DestinationClassNameMapper INSTANCE = Mappers.getMapper( DestinationClassNameMapper.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java index ef44767379..454e5e15bc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorated.java @@ -17,5 +17,5 @@ public interface DestinationClassNameMapperDecorated { DestinationClassNameMapperDecorated INSTANCE = Mappers.getMapper( DestinationClassNameMapperDecorated.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java index 216786da24..3ba1433e21 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperDecorator.java @@ -16,7 +16,7 @@ protected DestinationClassNameMapperDecorator(DestinationClassNameMapperDecorate } @Override - public String intToString(Integer source) { - return delegate.intToString( source ); + public Target map(Integer source) { + return delegate.map( source ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java index d76392d11d..d13675f0ab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfig.java @@ -15,5 +15,5 @@ public interface DestinationClassNameMapperWithConfig { DestinationClassNameMapperWithConfig INSTANCE = Mappers.getMapper( DestinationClassNameMapperWithConfig.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java index b218fff158..a3f845462b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameMapperWithConfigOverride.java @@ -16,5 +16,5 @@ public interface DestinationClassNameMapperWithConfigOverride { DestinationClassNameMapperWithConfigOverride INSTANCE = Mappers.getMapper( DestinationClassNameMapperWithConfigOverride.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java index a109ae0fc0..1f6e255b95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameTest.java @@ -5,35 +5,35 @@ */ package org.mapstruct.ap.test.destination; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Assert; -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 org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.factory.Mappers; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + /** * @author Christophe Labouisse on 27/05/2015. */ -@RunWith(AnnotationProcessorTestRunner.class) +@WithClasses( Target.class ) public class DestinationClassNameTest { - @Test + @ProcessorTest @WithClasses({ DestinationClassNameMapper.class }) - public void shouldGenerateRightName() throws Exception { + public void shouldGenerateRightName() { DestinationClassNameMapper instance = DestinationClassNameMapper.INSTANCE; assertThat( instance.getClass().getSimpleName() ).isEqualTo( "MyDestinationClassNameMapperCustomImpl" ); } - @Test + @ProcessorTest @WithClasses({ DestinationClassNameWithJsr330Mapper.class }) + @WithJavaxInject public void shouldNotGenerateSpi() throws Exception { Class clazz = DestinationClassNameWithJsr330Mapper.class; try { Mappers.getMapper( clazz ); - Assert.fail( "Should have thrown an ClassNotFoundException" ); + fail( "Should have thrown an ClassNotFoundException" ); } catch ( RuntimeException e ) { assertThat( e.getCause() ).isNotNull() @@ -46,25 +46,25 @@ public void shouldNotGenerateSpi() throws Exception { assertThat( instance.getClass().getSimpleName() ).isEqualTo( "DestinationClassNameWithJsr330MapperJsr330Impl" ); } - @Test + @ProcessorTest @WithClasses({ DestinationClassNameMapperConfig.class, DestinationClassNameMapperWithConfig.class }) - public void shouldGenerateRightNameWithConfig() throws Exception { + public void shouldGenerateRightNameWithConfig() { DestinationClassNameMapperWithConfig instance = DestinationClassNameMapperWithConfig.INSTANCE; assertThat( instance.getClass().getSimpleName() ) .isEqualTo( "MyDestinationClassNameMapperWithConfigConfigImpl" ); } - @Test + @ProcessorTest @WithClasses({ DestinationClassNameMapperConfig.class, DestinationClassNameMapperWithConfigOverride.class }) - public void shouldGenerateRightNameWithConfigOverride() throws Exception { + public void shouldGenerateRightNameWithConfigOverride() { DestinationClassNameMapperWithConfigOverride instance = DestinationClassNameMapperWithConfigOverride.INSTANCE; assertThat( instance.getClass().getSimpleName() ) .isEqualTo( "CustomDestinationClassNameMapperWithConfigOverrideMyImpl" ); } - @Test + @ProcessorTest @WithClasses({ DestinationClassNameMapperDecorated.class, DestinationClassNameMapperDecorator.class }) - public void shouldGenerateRightNameWithDecorator() throws Exception { + public void shouldGenerateRightNameWithDecorator() { DestinationClassNameMapperDecorated instance = DestinationClassNameMapperDecorated.INSTANCE; assertThat( instance.getClass().getSimpleName() ) .isEqualTo( "MyDestinationClassNameMapperDecoratedCustomImpl" ); @@ -73,7 +73,7 @@ public void shouldGenerateRightNameWithDecorator() throws Exception { .isEqualTo( "MyDestinationClassNameMapperDecoratedCustomImpl_" ); } - @Test + @ProcessorTest @WithClasses({ AbstractDestinationClassNameMapper.class, AbstractDestinationPackageNameMapper.class }) public void shouldWorkWithAbstractClasses() { AbstractDestinationClassNameMapper mapper1 = AbstractDestinationClassNameMapper.INSTANCE; diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java index 48d5547ff2..3358f68fd4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationClassNameWithJsr330Mapper.java @@ -6,11 +6,12 @@ package org.mapstruct.ap.test.destination; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; /** * @author Christophe Labouisse on 27/05/2015. */ -@Mapper(implementationName = "Jsr330Impl", componentModel = "jsr330") +@Mapper(implementationName = "Jsr330Impl", componentModel = MappingConstants.ComponentModel.JSR330) public interface DestinationClassNameWithJsr330Mapper { - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java index 8c4740e323..c049036a13 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapper.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapper { DestinationPackageNameMapper INSTANCE = Mappers.getMapper( DestinationPackageNameMapper.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java index 1dd371b48a..98f6236027 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorated.java @@ -17,5 +17,5 @@ public interface DestinationPackageNameMapperDecorated { DestinationPackageNameMapperDecorated INSTANCE = Mappers.getMapper( DestinationPackageNameMapperDecorated.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java index 4f6dd3242b..667bd99349 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperDecorator.java @@ -16,7 +16,7 @@ protected DestinationPackageNameMapperDecorator(DestinationPackageNameMapperDeco } @Override - public String intToString(Integer source) { - return delegate.intToString( source ); + public Target map(Integer source) { + return delegate.map( source ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java index fbe69ce43e..6a5a870520 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfig.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapperWithConfig { DestinationPackageNameMapperWithConfig INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithConfig.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java index b53dcaae42..79699006b1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithConfigOverride.java @@ -16,5 +16,5 @@ public interface DestinationPackageNameMapperWithConfigOverride { DestinationPackageNameMapperWithConfigOverride INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithConfigOverride.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java index 2a221e5f5b..068f30331d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameMapperWithSuffix.java @@ -15,5 +15,5 @@ public interface DestinationPackageNameMapperWithSuffix { DestinationPackageNameMapperWithSuffix INSTANCE = Mappers.getMapper( DestinationPackageNameMapperWithSuffix.class ); - String intToString(Integer source); + Target map(Integer source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java index d789a7024e..5e02c88d93 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/DestinationPackageNameTest.java @@ -5,47 +5,45 @@ */ package org.mapstruct.ap.test.destination; -import static org.assertj.core.api.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; /** * @author Christophe Labouisse on 27/05/2015. */ @IssueKey( "556" ) -@RunWith(AnnotationProcessorTestRunner.class) +@WithClasses( Target.class ) public class DestinationPackageNameTest { - @Test + @ProcessorTest @WithClasses({ DestinationPackageNameMapper.class }) - public void shouldGenerateInRightPackage() throws Exception { + public void shouldGenerateInRightPackage() { DestinationPackageNameMapper instance = DestinationPackageNameMapper.INSTANCE; assertThat( instance.getClass().getName() ) .isEqualTo( "org.mapstruct.ap.test.destination.dest.DestinationPackageNameMapperImpl" ); } - @Test + @ProcessorTest @WithClasses({ DestinationPackageNameMapperWithSuffix.class }) - public void shouldGenerateInRightPackageWithSuffix() throws Exception { + public void shouldGenerateInRightPackageWithSuffix() { DestinationPackageNameMapperWithSuffix instance = DestinationPackageNameMapperWithSuffix.INSTANCE; assertThat( instance.getClass().getName() ) .isEqualTo( "org.mapstruct.ap.test.destination.dest.DestinationPackageNameMapperWithSuffixMyImpl" ); } - @Test + @ProcessorTest @WithClasses({ DestinationPackageNameMapperConfig.class, DestinationPackageNameMapperWithConfig.class }) - public void shouldGenerateRightSuffixWithConfig() throws Exception { + public void shouldGenerateRightSuffixWithConfig() { DestinationPackageNameMapperWithConfig instance = DestinationPackageNameMapperWithConfig.INSTANCE; assertThat( instance.getClass().getName() ) .isEqualTo( "org.mapstruct.ap.test.destination.dest.DestinationPackageNameMapperWithConfigImpl" ); } - @Test + @ProcessorTest @WithClasses({ DestinationPackageNameMapperConfig.class, DestinationPackageNameMapperWithConfigOverride.class }) - public void shouldGenerateRightSuffixWithConfigOverride() throws Exception { + public void shouldGenerateRightSuffixWithConfigOverride() { DestinationPackageNameMapperWithConfigOverride instance = DestinationPackageNameMapperWithConfigOverride.INSTANCE; assertThat( instance.getClass().getName() ) @@ -54,9 +52,9 @@ public void shouldGenerateRightSuffixWithConfigOverride() throws Exception { ); } - @Test + @ProcessorTest @WithClasses({ DestinationPackageNameMapperDecorated.class, DestinationPackageNameMapperDecorator.class }) - public void shouldGenerateRightSuffixWithDecorator() throws Exception { + public void shouldGenerateRightSuffixWithDecorator() { DestinationPackageNameMapperDecorated instance = DestinationPackageNameMapperDecorated.INSTANCE; assertThat( instance.getClass().getName() ) .isEqualTo( "org.mapstruct.ap.test.destination.dest.DestinationPackageNameMapperDecoratedImpl" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java b/processor/src/test/java/org/mapstruct/ap/test/destination/Target.java new file mode 100644 index 0000000000..5b3bb4ba34 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/destination/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.destination; + +public class Target { + + private final Integer source; + + public Target(Integer source) { + this.source = source; + } + + public Integer getSource() { + return source; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.java new file mode 100644 index 0000000000..f1b3a0f073 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTarget.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.emptytarget; + +public class EmptyTarget { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.java new file mode 100644 index 0000000000..8c82f81b7b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetMapper.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.emptytarget; + +import org.mapstruct.Mapper; + +@Mapper +public interface EmptyTargetMapper { + + TargetWithNoSetters mapToTargetWithSetters(Source source); + + EmptyTarget mapToEmptyTarget(Source source); + + Target mapToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.java new file mode 100644 index 0000000000..66fa65b796 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/EmptyTargetTest.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.emptytarget; + +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("1140") +@WithClasses({ + EmptyTarget.class, + EmptyTargetMapper.class, + Source.class, + Target.class, + TargetWithNoSetters.class, +}) +class EmptyTargetTest { + + @ProcessorTest + @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = EmptyTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 13, + message = "No target property found for target \"TargetWithNoSetters\"."), + @Diagnostic(type = EmptyTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 15, + message = "No target property found for target \"EmptyTarget\".") + }) + void shouldProvideWarnings() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.java new file mode 100644 index 0000000000..643cdccf3a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Source.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.emptytarget; + +public class Source { + private String label; + private double weight; + private Object content; + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } + + public double getWeight() { + return weight; + } + + public void setWeight(double weight) { + this.weight = weight; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.java new file mode 100644 index 0000000000..488a1fa8a6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/Target.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.emptytarget; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private final String label; + private final double weight; + private final Object content; + + public Target(String label, double weight, Object content) { + this.label = label; + this.weight = weight; + this.content = content; + } + + public String getLabel() { + return label; + } + + public double getWeight() { + return weight; + } + + public Object getContent() { + return content; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.java new file mode 100644 index 0000000000..edeb80f0d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/emptytarget/TargetWithNoSetters.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.emptytarget; + +public class TargetWithNoSetters { + private int flightNumber; + private String airplaneName; + + public String getAirplaneName() { + return airplaneName; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/EnumMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/enums/EnumMappingTest.java deleted file mode 100644 index 552fad686e..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/EnumMappingTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -import static org.assertj.core.api.Assertions.assertThat; - -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.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 the generation and invocation of enum mapping methods. - * - * @author Gunnar Morling - */ -@IssueKey("128") -@WithClasses({ OrderEntity.class, OrderType.class, OrderDto.class, ExternalOrderType.class }) -@RunWith(AnnotationProcessorTestRunner.class) -public class EnumMappingTest { - - @Test - @WithClasses( OrderMapper.class ) - @ExpectedCompilationOutcome( - value = CompilationResult.SUCCEEDED, - diagnostics = { - @Diagnostic(type = OrderMapper.class, - kind = Kind.WARNING, - line = 28, - messageRegExp = "Mapping of Enums via @Mapping is going to be removed in future versions of " - + "MapStruct\\. Please use @ValueMapping instead!") - } - ) - public void shouldGenerateEnumMappingMethod() { - ExternalOrderType target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.B2B ); - assertThat( target ).isEqualTo( ExternalOrderType.B2B ); - - target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.RETAIL ); - assertThat( target ).isEqualTo( ExternalOrderType.RETAIL ); - } - - @Test - @WithClasses(OrderMapper.class) - @ExpectedCompilationOutcome( - value = CompilationResult.SUCCEEDED, - diagnostics = { - @Diagnostic(type = OrderMapper.class, - kind = Kind.WARNING, - line = 28, - messageRegExp = "Mapping of Enums via @Mapping is going to be removed in future versions of " - + "MapStruct\\. Please use @ValueMapping instead!") - } - ) - public void shouldConsiderConstantMappings() { - ExternalOrderType target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.EXTRA ); - assertThat( target ).isEqualTo( ExternalOrderType.SPECIAL ); - - target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.STANDARD ); - assertThat( target ).isEqualTo( ExternalOrderType.DEFAULT ); - - target = OrderMapper.INSTANCE.orderTypeToExternalOrderType( OrderType.NORMAL ); - assertThat( target ).isEqualTo( ExternalOrderType.DEFAULT ); - } - - @Test - @WithClasses( OrderMapper.class ) - @ExpectedCompilationOutcome( - value = CompilationResult.SUCCEEDED, - diagnostics = { - @Diagnostic(type = OrderMapper.class, - kind = Kind.WARNING, - line = 28, - messageRegExp = "Mapping of Enums via @Mapping is going to be removed in future versions of " - + "MapStruct\\. Please use @ValueMapping instead!") - } - ) - public void shouldInvokeEnumMappingMethodForPropertyMapping() { - OrderEntity order = new OrderEntity(); - order.setOrderType( OrderType.EXTRA ); - - OrderDto orderDto = OrderMapper.INSTANCE.orderEntityToDto( order ); - assertThat( orderDto ).isNotNull(); - assertThat( orderDto.getOrderType() ).isEqualTo( ExternalOrderType.SPECIAL ); - } - - @Test - @WithClasses( ErroneousOrderMapperMappingSameConstantTwice.class ) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - - @Diagnostic(type = ErroneousOrderMapperMappingSameConstantTwice.class, - kind = Kind.ERROR, - line = 29, - messageRegExp = "One enum constant must not be mapped to more than one target constant, but " + - "constant EXTRA is mapped to SPECIAL, DEFAULT\\."), - @Diagnostic(type = ErroneousOrderMapperMappingSameConstantTwice.class, - kind = Kind.WARNING, - line = 29, - messageRegExp = "Mapping of Enums via @Mapping is going to be removed in future versions of " - + "MapStruct\\. Please use @ValueMapping instead!") - } - ) - public void shouldRaiseErrorIfSameSourceEnumConstantIsMappedTwice() { - } - - @Test - @WithClasses(ErroneousOrderMapperUsingUnknownEnumConstants.class) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = ErroneousOrderMapperUsingUnknownEnumConstants.class, - kind = Kind.WARNING, - line = 27, - messageRegExp = "Mapping of Enums via @Mapping is going to be removed in future versions of " - + "MapStruct\\. Please use @ValueMapping instead!"), - @Diagnostic(type = ErroneousOrderMapperUsingUnknownEnumConstants.class, - kind = Kind.ERROR, - line = 24, - messageRegExp = "Constant FOO doesn't exist in enum type org.mapstruct.ap.test.enums.OrderType\\."), - @Diagnostic(type = ErroneousOrderMapperUsingUnknownEnumConstants.class, - kind = Kind.ERROR, - line = 25, - messageRegExp = "Constant BAR doesn't exist in enum type org.mapstruct.ap.test.enums." + - "ExternalOrderType\\.") - } - ) - public void shouldRaiseErrorIfUnknownEnumConstantsAreSpecifiedInMapping() { - } - - @Test - @WithClasses(ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType.class) - @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType.class, - kind = Kind.ERROR, - line = 21, - messageRegExp = "The following constants from the source enum have no corresponding constant in the " + - "target enum and must be be mapped via adding additional mappings: EXTRA, STANDARD, NORMAL") - } - ) - public void shouldRaiseErrorIfSourceConstantWithoutMatchingConstantInTargetTypeIsNotMapped() { - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperMappingSameConstantTwice.java b/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperMappingSameConstantTwice.java deleted file mode 100644 index d63ca71fae..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperMappingSameConstantTwice.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -/** - * @author Gunnar Morling - */ -@Mapper -public interface ErroneousOrderMapperMappingSameConstantTwice { - - ErroneousOrderMapperMappingSameConstantTwice INSTANCE = Mappers.getMapper( - ErroneousOrderMapperMappingSameConstantTwice.class - ); - - @Mappings({ - @Mapping(source = "EXTRA", target = "SPECIAL"), - @Mapping(source = "EXTRA", target = "DEFAULT"), - @Mapping(source = "STANDARD", target = "DEFAULT"), - @Mapping(source = "NORMAL", target = "DEFAULT") - }) - ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType.java b/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType.java deleted file mode 100644 index d086e91819..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -/** - * @author Gunnar Morling - */ -@Mapper -public interface ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType { - - ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType INSTANCE = Mappers.getMapper( - ErroneousOrderMapperNotMappingConstantWithoutMatchInTargetType.class - ); - - ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperUsingUnknownEnumConstants.java b/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperUsingUnknownEnumConstants.java deleted file mode 100644 index 79747b2ae9..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/ErroneousOrderMapperUsingUnknownEnumConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -/** - * @author Gunnar Morling - */ -@Mapper -public interface ErroneousOrderMapperUsingUnknownEnumConstants { - - ErroneousOrderMapperUsingUnknownEnumConstants INSTANCE = Mappers.getMapper( - ErroneousOrderMapperUsingUnknownEnumConstants.class - ); - - @Mappings({ - @Mapping(source = "FOO", target = "SPECIAL"), - @Mapping(source = "EXTRA", target = "BAR") - }) - ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/ExternalOrderType.java b/processor/src/test/java/org/mapstruct/ap/test/enums/ExternalOrderType.java deleted file mode 100644 index 827b668354..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/ExternalOrderType.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -/** - * @author Gunnar Morling - */ -public enum ExternalOrderType { - - RETAIL, B2B, SPECIAL, DEFAULT -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/OrderDto.java b/processor/src/test/java/org/mapstruct/ap/test/enums/OrderDto.java deleted file mode 100644 index e9ee6429d8..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/OrderDto.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -/** - * @author Gunnar Morling - */ -public class OrderDto { - - private ExternalOrderType orderType; - - public ExternalOrderType getOrderType() { - return orderType; - } - - public void setOrderType(ExternalOrderType orderType) { - this.orderType = orderType; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/OrderEntity.java b/processor/src/test/java/org/mapstruct/ap/test/enums/OrderEntity.java deleted file mode 100644 index 3d1a6b1dba..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/OrderEntity.java +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -/** - * @author Gunnar Morling - */ -public class OrderEntity { - - private OrderType orderType; - - public OrderType getOrderType() { - return orderType; - } - - public void setOrderType(OrderType orderType) { - this.orderType = orderType; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/OrderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/enums/OrderMapper.java deleted file mode 100644 index 4c65db6a1a..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/OrderMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; -import org.mapstruct.factory.Mappers; - -/** - * @author Gunnar Morling - */ -@Mapper -public interface OrderMapper { - - OrderMapper INSTANCE = Mappers.getMapper( OrderMapper.class ); - - OrderDto orderEntityToDto(OrderEntity order); - - @Mappings({ - @Mapping(source = "EXTRA", target = "SPECIAL"), - @Mapping(source = "STANDARD", target = "DEFAULT"), - @Mapping(source = "NORMAL", target = "DEFAULT") - }) - ExternalOrderType orderTypeToExternalOrderType(OrderType orderType); -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/enums/OrderType.java b/processor/src/test/java/org/mapstruct/ap/test/enums/OrderType.java deleted file mode 100644 index da519d8e28..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/enums/OrderType.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.enums; - -/** - * @author Gunnar Morling - */ -public enum OrderType { - - RETAIL, B2B, EXTRA, STANDARD, NORMAL -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousAnnotatedFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousAnnotatedFactoryTest.java index 64191955cb..99fd2b98cd 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousAnnotatedFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousAnnotatedFactoryTest.java @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod; -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 Remo Meier @@ -20,28 +18,19 @@ Bar.class, Foo.class, AmbiguousBarFactory.class, Source.class, SourceTargetMapperAndBarFactory.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class AmbiguousAnnotatedFactoryTest { - @Test + @ProcessorTest @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = SourceTargetMapperAndBarFactory.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 22, - messageRegExp = "Ambiguous factory methods found for creating " - + "org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod.Bar: " - + "org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod.Bar " - + "createBar\\(org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod.Foo foo\\), " - + "org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod.Bar " - + ".*AmbiguousBarFactory.createBar\\(org.mapstruct.ap.test.erroneous." - + "ambiguousannotatedfactorymethod.Foo foo\\)."), - @Diagnostic(type = SourceTargetMapperAndBarFactory.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 22, - messageRegExp = ".*\\.ambiguousannotatedfactorymethod.Bar does not have an accessible parameterless " + - "constructor\\.") + message = "Ambiguous factory methods found for creating Bar: " + + "Bar createBar(Foo foo), " + + "Bar AmbiguousBarFactory.createBar(Foo foo)." + + " See https://mapstruct.org/faq/#ambiguous for more info.") } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousBarFactory.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousBarFactory.java index cb1c30a2d6..2c6c070402 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousBarFactory.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/AmbiguousBarFactory.java @@ -7,7 +7,6 @@ import org.mapstruct.ObjectFactory; - /** * @author Remo Meier */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Foo.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Foo.java index 796dd0fae7..679efa8358 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Foo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Foo.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod; - /** * @author Remo Meier */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Source.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Source.java index 8fdd2ad52e..0a4e64dc54 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Source.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod; - /** * @author Remo Meier */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Target.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Target.java index 71b5880add..f15dc0e8d6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousannotatedfactorymethod/Target.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.erroneous.ambiguousannotatedfactorymethod; - /** * @author Remo Meier */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/FactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/FactoryTest.java index e54602b01a..b9ae5b8ffa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/FactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/FactoryTest.java @@ -5,15 +5,13 @@ */ package org.mapstruct.ap.test.erroneous.ambiguousfactorymethod; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.a.BarFactory; 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 @@ -23,10 +21,9 @@ Bar.class, Foo.class, BarFactory.class, Source.class, SourceTargetMapperAndBarFactory.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class FactoryTest { - @Test + @ProcessorTest @IssueKey("81") @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -34,16 +31,10 @@ public class FactoryTest { @Diagnostic(type = SourceTargetMapperAndBarFactory.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 22, - messageRegExp = "Ambiguous factory methods found for creating " - + "org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar: " - + "org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar createBar\\(\\), " - + "org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar .*BarFactory.createBar\\(\\)."), - @Diagnostic(type = SourceTargetMapperAndBarFactory.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 22, - messageRegExp = ".*\\.ambiguousfactorymethod\\.Bar does not have an accessible parameterless " - + "constructor\\.") - + message = "Ambiguous factory methods found for creating Bar: " + + "Bar createBar(), " + + "Bar BarFactory.createBar()." + + " See https://mapstruct.org/faq/#ambiguous for more info.") } ) public void shouldUseTwoFactoryMethods() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Foo.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Foo.java index 2373a6549f..7d13089921 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Foo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Foo.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.erroneous.ambiguousfactorymethod; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Source.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Source.java index ea0c9a247e..2aabe7e06d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Source.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.erroneous.ambiguousfactorymethod; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Target.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Target.java index 2342d880eb..025ae810f8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/Target.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.erroneous.ambiguousfactorymethod; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/a/BarFactory.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/a/BarFactory.java index 35cb5c4462..b0e2a46e48 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/a/BarFactory.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousfactorymethod/a/BarFactory.java @@ -7,7 +7,6 @@ import org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/AmbiguousMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/AmbiguousMapperTest.java new file mode 100644 index 0000000000..c3fa15b244 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/AmbiguousMapperTest.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.test.erroneous.ambiguousmapping; + +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.compilation.annotation.ProcessorOption; + +@IssueKey("2156") +public class AmbiguousMapperTest { + + @ProcessorTest + @WithClasses( ErroneousWithAmbiguousMethodsMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousWithAmbiguousMethodsMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 16, + message = + "Ambiguous mapping methods found for mapping property " + + "\"ErroneousWithAmbiguousMethodsMapper.LeafDTO branch.leaf\" to ErroneousWithAmbiguousMethodsMapper.LeafEntity: " + + "ErroneousWithAmbiguousMethodsMapper.LeafEntity map1(ErroneousWithAmbiguousMethodsMapper.LeafDTO dto), " + + "ErroneousWithAmbiguousMethodsMapper.LeafEntity map2(ErroneousWithAmbiguousMethodsMapper.LeafDTO dto). " + + "See https://mapstruct.org/faq/#ambiguous for more info." ) + } + ) + public void testErrorMessageForAmbiguous() { + } + + @ProcessorTest + @WithClasses( ErroneousWithMoreThanFiveAmbiguousMethodsMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousWithMoreThanFiveAmbiguousMethodsMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = + // CHECKSTYLE:OFF + "Ambiguous mapping methods found for mapping property \"ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO branch.leaf\" to " + + "ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity: " + + "ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map1(ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map2(ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map3(ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map4(ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map5(ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto)" + + "... and 1 more. See https://mapstruct.org/faq/#ambiguous for more info." + // CHECKSTYLE:ON + ) + } + ) + public void testErrorMessageForManyAmbiguous() { + } + + @ProcessorTest + @ProcessorOption(name = "mapstruct.verbose", value = "true") + @WithClasses( ErroneousWithMoreThanFiveAmbiguousMethodsMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousWithMoreThanFiveAmbiguousMethodsMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = + // CHECKSTYLE:OFF + "Ambiguous mapping methods found for mapping property \"org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO branch.leaf\" " + + "to org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity: " + + "org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map1(org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map2(org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map3(org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map4(org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map5(org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto), " + + "org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafEntity map6(org.mapstruct.ap.test.erroneous.ambiguousmapping.ErroneousWithMoreThanFiveAmbiguousMethodsMapper.LeafDTO dto). " + + "See https://mapstruct.org/faq/#ambiguous for more info." + // CHECKSTYLE:ON + ) + } + ) + public void testErrorMessageForManyAmbiguousVerbose() { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/ErroneousWithAmbiguousMethodsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/ErroneousWithAmbiguousMethodsMapper.java new file mode 100644 index 0000000000..165f8d5945 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/ErroneousWithAmbiguousMethodsMapper.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.erroneous.ambiguousmapping; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousWithAmbiguousMethodsMapper { + + ErroneousWithAmbiguousMethodsMapper INSTANCE = Mappers.getMapper( ErroneousWithAmbiguousMethodsMapper.class ); + + TrunkEntity map(TrunkDTO dto); + + default LeafEntity map1(LeafDTO dto) { + return new LeafEntity(); + } + + // duplicated method, triggering ambiguous mapping method + default LeafEntity map2(LeafDTO dto) { + return new LeafEntity(); + } + + // CHECKSTYLE:OFF + class TrunkDTO { + public BranchDTO branch; + } + + class BranchDTO { + public LeafDTO leaf; + } + + class LeafDTO { + public int numberOfVeigns; + } + + class TrunkEntity { + public BranchEntity branch; + } + + class BranchEntity { + public LeafEntity leaf; + } + + class LeafEntity { + public int numberOfVeigns; + } + // CHECKSTYLE ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/ErroneousWithMoreThanFiveAmbiguousMethodsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/ErroneousWithMoreThanFiveAmbiguousMethodsMapper.java new file mode 100644 index 0000000000..2ca70ccaf4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/ambiguousmapping/ErroneousWithMoreThanFiveAmbiguousMethodsMapper.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.erroneous.ambiguousmapping; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousWithMoreThanFiveAmbiguousMethodsMapper { + + ErroneousWithMoreThanFiveAmbiguousMethodsMapper + INSTANCE = Mappers.getMapper( ErroneousWithMoreThanFiveAmbiguousMethodsMapper.class ); + + TrunkEntity map(TrunkDTO dto); + + default LeafEntity map1(LeafDTO dto) { + return new LeafEntity(); + } + + // duplicated method, triggering ambiguous mapping method + default LeafEntity map2(LeafDTO dto) { + return new LeafEntity(); + } + + // duplicated method, triggering ambiguous mapping method + default LeafEntity map3(LeafDTO dto) { + return new LeafEntity(); + } + + // duplicated method, triggering ambiguous mapping method + default LeafEntity map4(LeafDTO dto) { + return new LeafEntity(); + } + + // duplicated method, triggering ambiguous mapping method + + default LeafEntity map5(LeafDTO dto) { + return new LeafEntity(); + } + + // duplicated method, triggering ambiguous mapping method + default LeafEntity map6(LeafDTO dto) { + return new LeafEntity(); + } + + // CHECKSTYLE:OFF + class TrunkDTO { + public BranchDTO branch; + } + + class BranchDTO { + public LeafDTO leaf; + } + + class LeafDTO { + public int numberOfVeigns; + } + + class TrunkEntity { + public BranchEntity branch; + } + + class BranchEntity { + public LeafEntity leaf; + } + + class LeafEntity { + public int numberOfVeigns; + } + // CHECKSTYLE ON +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/annotationnotfound/AnnotationNotFoundTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/annotationnotfound/AnnotationNotFoundTest.java index 50d5b7c82b..8a5ffefe6d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/annotationnotfound/AnnotationNotFoundTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/annotationnotfound/AnnotationNotFoundTest.java @@ -7,14 +7,12 @@ 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; /** * Test for (custom / external) annotation that is not on class path @@ -22,10 +20,9 @@ * @author Sjaak Derksen */ @WithClasses( { Source.class, Target.class, ErroneousMapper.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class AnnotationNotFoundTest { - @Test + @ProcessorTest @IssueKey( "298" ) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper.java index fdb665552e..d7e230547a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper.java @@ -13,11 +13,15 @@ public interface ErroneousMapper { @Mappings({ - @Mapping(source = "bar", target = "foo"), - @Mapping(source = "source1.foo", target = "foo"), - @Mapping(source = "foo", target = "bar") + @Mapping(target = "foo", source = "bar"), + @Mapping(target = "foo", source = "source1.foo" ), + @Mapping(target = "bar", source = "foo") }) Target sourceToTarget(Source source); + // to test that nested is also reported correctly + @Mapping(target = "foo", source = "source1.foo" ) + Target sourceToTarget2(Source source); + AnotherTarget sourceToAnotherTarget(Source source); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper1.java index ce9134cc9a..502ac44b1f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper1.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper1.java @@ -13,7 +13,7 @@ public interface ErroneousMapper1 { @Mappings({ - @Mapping(source = "source.foobar", target = "foo") + @Mapping(target = "foo", source = "source.foobar") }) Target sourceToTarget(Source source, DummySource source1); diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMappingsTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMappingsTest.java index 8031a7880d..0d040f7366 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMappingsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMappingsTest.java @@ -7,14 +7,12 @@ 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; /** * Test for using unknown attributes in {@code @Mapping}. @@ -22,10 +20,9 @@ * @author Gunnar Morling */ @WithClasses({ Source.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ErroneousMappingsTest { - @Test + @ProcessorTest @IssueKey("11") @WithClasses( { ErroneousMapper.class, AnotherTarget.class } ) @ExpectedCompilationOutcome( @@ -33,33 +30,32 @@ public class ErroneousMappingsTest { diagnostics = { @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, - line = 16, - messageRegExp = "No property named \"bar\" exists in source parameter\\(s\\)\\. " + - "Did you mean \"foo\"?"), + line = 20, + message = "Target property \"foo\" must not be mapped more than once."), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, - line = 17, - messageRegExp = "No property named \"source1.foo\" exists in source parameter\\(s\\)\\. " + + line = 16, + message = "No property named \"bar\" exists in source parameter(s). " + "Did you mean \"foo\"?"), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, line = 18, - messageRegExp = "Unknown property \"bar\" in result type " + - "org.mapstruct.ap.test.erroneous.attributereference.Target. Did you mean \"foo\"?"), + message = "Unknown property \"bar\" in result type Target. Did you mean \"foo\"?"), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, - line = 20, - messageRegExp = "Target property \"foo\" must not be mapped more than once"), + line = 23, + message = "No property named \"source1.foo\" exists in source parameter(s). " + + "Did you mean \"foo\"?"), @Diagnostic(type = ErroneousMapper.class, kind = Kind.WARNING, - line = 22, - messageRegExp = "Unmapped target property: \"bar\"") + line = 26, + message = "Unmapped target property: \"bar\".") } ) public void shouldFailToGenerateMappings() { } - @Test + @ProcessorTest @WithClasses( { ErroneousMapper1.class, DummySource.class } ) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -67,13 +63,13 @@ public void shouldFailToGenerateMappings() { @Diagnostic(type = ErroneousMapper1.class, kind = Kind.ERROR, line = 16, - messageRegExp = "The type of parameter \"source\" has no property named \"foobar\"") + message = "The type of parameter \"source\" has no property named \"foobar\".") } ) public void shouldFailToGenerateMappingsErrorOnMandatoryParameterName() { } - @Test + @ProcessorTest @WithClasses( { ErroneousMapper2.class } ) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -81,7 +77,7 @@ public void shouldFailToGenerateMappingsErrorOnMandatoryParameterName() { @Diagnostic(type = ErroneousMapper2.class, kind = Kind.ERROR, line = 19, - messageRegExp = "Target property \"foo\" must not be mapped more than once" ) + message = "Target property \"foo\" must not be mapped more than once." ) } ) public void shouldFailToGenerateMappingsErrorOnDuplicateTarget() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/misbalancedbraces/MisbalancedBracesTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/misbalancedbraces/MisbalancedBracesTest.java index bc270d446e..f3f6e2417a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/misbalancedbraces/MisbalancedBracesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/misbalancedbraces/MisbalancedBracesTest.java @@ -7,15 +7,13 @@ 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.DisableCheckstyle; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; /** * Test for making sure that expressions with too many closing braces are passed through, letting the compiler raise an @@ -25,12 +23,11 @@ */ @WithClasses({ MapperWithMalformedExpression.class, Source.class, Target.class }) @DisableCheckstyle -@RunWith(AnnotationProcessorTestRunner.class) public class MisbalancedBracesTest { // the compiler messages due to the additional closing brace differ between JDK and Eclipse, hence we can only // assert on the line number but not the message - @Test + @ProcessorTest @IssueKey("1056") @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/propertymapping/ErroneousPropertyMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/propertymapping/ErroneousPropertyMappingTest.java index ed2e9e2108..43d7dfca5b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/propertymapping/ErroneousPropertyMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/propertymapping/ErroneousPropertyMappingTest.java @@ -5,62 +5,70 @@ */ package org.mapstruct.ap.test.erroneous.propertymapping; -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; @IssueKey("1504") -@WithClasses( { Source.class, Target.class, UnmappableClass.class } ) -@RunWith(AnnotationProcessorTestRunner.class) +@WithClasses({ Source.class, Target.class, UnmappableClass.class }) public class ErroneousPropertyMappingTest { - @Test - @WithClasses( ErroneousMapper1.class ) + @ProcessorTest + @WithClasses(ErroneousMapper1.class) @IssueKey("1504") @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(kind = javax.tools.Diagnostic.Kind.ERROR, - line = 16, - messageRegExp = ".*Consider to declare/implement a mapping method.*") } + line = 16, + message = "Can't map property \"UnmappableClass source\" to \"String property\". " + + "Consider to declare/implement a mapping method: \"String map(UnmappableClass value)\".") + } ) - public void testUnmappableSourceProperty() { } + public void testUnmappableSourceProperty() { + } - @Test - @WithClasses( ErroneousMapper2.class ) + @ProcessorTest + @WithClasses(ErroneousMapper2.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = ".*Consider to declare/implement a mapping method.*") } + message = "Can't map property \"UnmappableClass nameBasedSource\" to \"String nameBasedSource\"." + + " Consider to declare/implement a mapping method: \"String map(UnmappableClass value)\".") + } ) - public void testUnmappableSourcePropertyWithNoSourceDefinedInMapping() { } + public void testUnmappableSourcePropertyWithNoSourceDefinedInMapping() { + } - @Test - @WithClasses( ErroneousMapper3.class ) + @ProcessorTest + @WithClasses(ErroneousMapper3.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "Can't map.*constant.*" ) } + message = "Can't map \"constant\" to \"UnmappableClass constant\".") + } ) - public void testUnmappableConstantAssignment() { } + public void testUnmappableConstantAssignment() { + } - @Test - @WithClasses( ErroneousMapper4.class ) + @ProcessorTest + @WithClasses(ErroneousMapper4.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = ".*Consider to declare/implement a mapping method.*") } + message = "Can't map property \"UnmappableClass source\" to \"String property\". " + + "Consider to declare/implement a mapping method: \"String map(UnmappableClass value)\".") + } ) - public void testUnmappableParameterAssignment() { } + public void testUnmappableParameterAssignment() { + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/AbstractMapper.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/AbstractMapper.java new file mode 100644 index 0000000000..5155ac386c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/AbstractMapper.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.ap.test.erroneous.supermappingwithsubclassmapper; + +public interface AbstractMapper { + + TARGET map(SOURCE source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper1.java new file mode 100644 index 0000000000..95a0784934 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper1.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.erroneous.supermappingwithsubclassmapper; + +import org.mapstruct.Mapper; + +@Mapper +public interface ErroneousMapper1 extends AbstractMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper2.java new file mode 100644 index 0000000000..b75f1a0e73 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousMapper2.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.erroneous.supermappingwithsubclassmapper; + +import org.mapstruct.Mapper; + +@Mapper +public interface ErroneousMapper2 extends AbstractMapper { + @Override + Target map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousPropertyMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousPropertyMappingTest.java new file mode 100644 index 0000000000..147f1d6929 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/ErroneousPropertyMappingTest.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.erroneous.supermappingwithsubclassmapper; + +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( "598" ) +@WithClasses( { Source.class, Target.class, UnmappableClass.class, AbstractMapper.class } ) +public class ErroneousPropertyMappingTest { + + @ProcessorTest + @WithClasses( ErroneousMapper1.class ) + @IssueKey( "598" ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapper1.class, + line = 11, + messageRegExp = "Can't map property \"UnmappableClass property\" to \"String property\"\\. " + + "Consider to declare/implement a mapping method: \"String map\\(UnmappableClass value\\)\"\\. " + + "Occured at 'TARGET map\\(SOURCE source\\)' in 'AbstractMapper'\\.") + } + ) + public void testUnmappableSourcePropertyInSuperclass() { + } + + @ProcessorTest + @WithClasses( ErroneousMapper2.class ) + @IssueKey( "598" ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousMapper2.class, + line = 13, + message = "Can't map property \"UnmappableClass property\" to \"String property\". " + + "Consider to declare/implement a mapping method: \"String map(UnmappableClass value)\".") } ) + public void testMethodOverride() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Source.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Source.java new file mode 100644 index 0000000000..383eb1dbe6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/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.erroneous.supermappingwithsubclassmapper; + +public class Source { + + private UnmappableClass property; + + public UnmappableClass getProperty() { + return property; + } + + public void setProperty(UnmappableClass property) { + this.property = property; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Target.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/Target.java new file mode 100644 index 0000000000..f647ad7afa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/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.erroneous.supermappingwithsubclassmapper; + +public 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/erroneous/supermappingwithsubclassmapper/UnmappableClass.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/UnmappableClass.java new file mode 100644 index 0000000000..af239fb046 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/supermappingwithsubclassmapper/UnmappableClass.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.erroneous.supermappingwithsubclassmapper; + +public class UnmappableClass { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMappingsTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMappingsTest.java index 4462bdffa9..a045d16dfa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMappingsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMappingsTest.java @@ -7,14 +7,12 @@ 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; /** * Tests failures expected for unmappable attributes. @@ -22,10 +20,9 @@ * @author Gunnar Morling */ @WithClasses({ ErroneousMapper.class, Source.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ErroneousMappingsTest { - @Test + @ProcessorTest @IssueKey("6") @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -33,26 +30,26 @@ public class ErroneousMappingsTest { @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, line = 14, - messageRegExp = "Can't map property \"boolean foo\" to \"int foo\". Consider to declare/implement a " - + "mapping method: \"int map\\(boolean value\\)\"."), + message = "Can't map property \"boolean foo\" to \"int foo\". Consider to declare/implement a " + + "mapping method: \"int map(boolean value)\"."), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, line = 16, - messageRegExp = "Can't map property \"int foo\" to \"boolean foo\". Consider to declare/implement a " - + "mapping method: \"boolean map\\(int value\\)\"."), + message = "Can't map property \"int foo\" to \"boolean foo\". Consider to declare/implement a " + + "mapping method: \"boolean map(int value)\"."), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, line = 18, - messageRegExp = "Can't generate mapping method with primitive return type\\."), + message = "Can't generate mapping method with primitive return type."), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, line = 20, - messageRegExp = "Can't generate mapping method with primitive parameter type\\."), + message = "Can't generate mapping method with primitive parameter type."), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, line = 22, - messageRegExp = - "Can't generate mapping method that has a parameter annotated with @TargetType\\.") + message = + "Can't generate mapping method that has a parameter annotated with @TargetType.") } ) public void shouldFailToGenerateMappings() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/exceptions/ExceptionTest.java b/processor/src/test/java/org/mapstruct/ap/test/exceptions/ExceptionTest.java index 6e6d8b4e03..212893052a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/exceptions/ExceptionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/exceptions/ExceptionTest.java @@ -11,13 +11,13 @@ import java.util.List; import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.exceptions.imports.TestException1; import org.mapstruct.ap.test.exceptions.imports.TestExceptionBase; 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; /** * @@ -32,105 +32,115 @@ TestExceptionBase.class, TestException1.class, TestException2.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class ExceptionTest { - @Test( expected = RuntimeException.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowRuntimeInBeanMapping() throws TestException2, ParseException { Source source = new Source(); source.setSize( 1 ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.sourceToTarget( source ); + assertThatThrownBy( () -> sourceTargetMapper.sourceToTarget( source ) ) + .isInstanceOf( RuntimeException.class ); } - @Test( expected = TestException2.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowTestException2InBeanMapping() throws TestException2, ParseException { Source source = new Source(); source.setSize( 2 ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.sourceToTarget( source ); + assertThatThrownBy( () -> sourceTargetMapper.sourceToTarget( source ) ) + .isInstanceOf( TestException2.class ); } - @Test( expected = ParseException.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowTestParseExceptionInBeanMappingDueToTypeConverion() throws TestException2, ParseException { Source source = new Source(); source.setDate( "nonsense" ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.sourceToTarget( source ); + assertThatThrownBy( () -> sourceTargetMapper.sourceToTarget( source ) ) + .isInstanceOf( ParseException.class ); } - @Test( expected = RuntimeException.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowRuntimeInIterableMapping() throws TestException2 { - List source = new ArrayList(); + List source = new ArrayList<>(); source.add( 1 ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.integerListToLongList( source ); + assertThatThrownBy( () -> sourceTargetMapper.integerListToLongList( source ) ) + .isInstanceOf( RuntimeException.class ); } - @Test( expected = TestException2.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowTestException2InIterableMapping() throws TestException2 { - List source = new ArrayList(); + List source = new ArrayList<>(); source.add( 2 ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.integerListToLongList( source ); + assertThatThrownBy( () -> sourceTargetMapper.integerListToLongList( source ) ) + .isInstanceOf( TestException2.class ); } - @Test( expected = RuntimeException.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowRuntimeInMapKeyMapping() throws TestException2 { - Map source = new HashMap(); + Map source = new HashMap<>(); source.put( 1, "test" ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.integerKeyMapToLongKeyMap( source ); + assertThatThrownBy( () -> sourceTargetMapper.integerKeyMapToLongKeyMap( source ) ) + .isInstanceOf( RuntimeException.class ); } - @Test( expected = TestException2.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowTestException2InMapKeyMapping() throws TestException2 { - Map source = new HashMap(); + Map source = new HashMap<>(); source.put( 2, "test" ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.integerKeyMapToLongKeyMap( source ); + assertThatThrownBy( () -> sourceTargetMapper.integerKeyMapToLongKeyMap( source ) ) + .isInstanceOf( TestException2.class ); } - @Test( expected = RuntimeException.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowRuntimeInMapValueMapping() throws TestException2 { - Map source = new HashMap(); + Map source = new HashMap<>(); source.put( "test", 1 ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.integerValueMapToLongValueMap( source ); + assertThatThrownBy( () -> sourceTargetMapper.integerValueMapToLongValueMap( source ) ) + .isInstanceOf( RuntimeException.class ); } - @Test( expected = TestException2.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowTestException2InMapValueMapping() throws TestException2 { - Map source = new HashMap(); + Map source = new HashMap<>(); source.put( "test", 2 ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.integerValueMapToLongValueMap( source ); + assertThatThrownBy( () -> sourceTargetMapper.integerValueMapToLongValueMap( source ) ) + .isInstanceOf( TestException2.class ); } - @Test( expected = RuntimeException.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowRuntimeInBeanMappingViaBaseException() throws TestExceptionBase { Source source = new Source(); source.setSize( 1 ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.sourceToTargetViaBaseException( source ); + assertThatThrownBy( () -> sourceTargetMapper.sourceToTargetViaBaseException( source ) ) + .isInstanceOf( RuntimeException.class ); } - @Test( expected = TestException2.class ) + @ProcessorTest @IssueKey( "198" ) public void shouldThrowTestException2InBeanMappingViaBaseException() throws TestExceptionBase { Source source = new Source(); source.setSize( 2 ); SourceTargetMapper sourceTargetMapper = SourceTargetMapper.INSTANCE; - sourceTargetMapper.sourceToTargetViaBaseException( source ); + assertThatThrownBy( () -> sourceTargetMapper.sourceToTargetViaBaseException( source ) ) + .isInstanceOf( TestException2.class ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/exceptions/ExceptionTestMapper.java b/processor/src/test/java/org/mapstruct/ap/test/exceptions/ExceptionTestMapper.java index c856c0d3bf..13d0d1f5cf 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/exceptions/ExceptionTestMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/exceptions/ExceptionTestMapper.java @@ -20,6 +20,6 @@ public Long toLong(Integer size) throws TestException1, TestException2 { else if ( size == 2 ) { throw new TestException2(); } - return new Long(size); + return Long.valueOf( size ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryTest.java index e00f88198d..58cb0a1fcf 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryTest.java @@ -5,15 +5,11 @@ */ package org.mapstruct.ap.test.factories; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.ArrayList; 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.test.factories.a.BarFactory; import org.mapstruct.ap.test.factories.targettype.Bar9Base; import org.mapstruct.ap.test.factories.targettype.Bar9Child; @@ -21,8 +17,10 @@ import org.mapstruct.ap.test.factories.targettype.Foo9Base; import org.mapstruct.ap.test.factories.targettype.Foo9Child; 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; /** * @author Sjaak Derksen @@ -33,9 +31,8 @@ org.mapstruct.ap.test.factories.b.BarFactory.class, org.mapstruct.ap.test.factories.c.BarFactory.class, Bar9Factory.class, Source.class, SourceTargetMapperAndBar2Factory.class, Target.class, CustomList.class, CustomListImpl.class, CustomMap.class, CustomMapImpl.class, FactoryCreatable.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class FactoryTest { - @Test + @ProcessorTest public void shouldUseThreeFactoryMethods() { Target target = SourceTargetMapperAndBar2Factory.INSTANCE.sourceToTarget( createSource() ); @@ -84,17 +81,17 @@ private Source createSource() { foo4.setProp( "foo4" ); source.setProp4( foo4 ); - List fooList = new ArrayList(); + List fooList = new ArrayList<>(); fooList.add( "fooListEntry" ); source.setPropList( fooList ); - Map fooMap = new HashMap(); + Map fooMap = new HashMap<>(); fooMap.put( "key", "fooValue" ); source.setPropMap( fooMap ); return source; } - @Test + @ProcessorTest @IssueKey( "136" ) @WithClasses( { GenericFactory.class, SourceTargetMapperWithGenericFactory.class } ) public void shouldUseGenericFactory() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/SourceTargetMapperAndBar2Factory.java b/processor/src/test/java/org/mapstruct/ap/test/factories/SourceTargetMapperAndBar2Factory.java index 2e33bf3ff8..568b9abf2a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/factories/SourceTargetMapperAndBar2Factory.java +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/SourceTargetMapperAndBar2Factory.java @@ -40,10 +40,10 @@ public Bar2 createBar2() { } public CustomList createCustomList() { - return new CustomListImpl( "CUSTOMLIST" ); + return new CustomListImpl<>( "CUSTOMLIST" ); } public CustomMap createCustomMap() { - return new CustomMapImpl( "CUSTOMMAP" ); + return new CustomMapImpl<>( "CUSTOMMAP" ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/assignment/ParameterAssigmentFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/factories/assignment/ParameterAssigmentFactoryTest.java index 5ea82a5096..41140a00a4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/factories/assignment/ParameterAssigmentFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/assignment/ParameterAssigmentFactoryTest.java @@ -5,22 +5,19 @@ */ package org.mapstruct.ap.test.factories.assignment; -import static org.assertj.core.api.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; /** * @author Remo Meier */ @WithClasses( { Bar5.class, Foo5A.class, Foo5B.class, Bar6.class, Foo6A.class, Foo6B.class, Bar7.class, Foo7A.class, Foo7B.class, Bar5Factory.class, Bar6Factory.class, Bar7Factory.class, ParameterAssignmentFactoryTestMapper.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class ParameterAssigmentFactoryTest { - @Test + @ProcessorTest public void shouldUseFactoryMethodWithMultipleParams() { Foo5A foo5a = new Foo5A(); foo5a.setPropB( "foo5a" ); @@ -38,7 +35,7 @@ public void shouldUseFactoryMethodWithMultipleParams() { assertThat( bar5.getSomeTypeProp1() ).isEqualTo( "foo5b" ); } - @Test + @ProcessorTest public void shouldUseFactoryMethodWithFirstParamsOfMappingMethod() { Foo6A foo6a = new Foo6A(); foo6a.setPropB( "foo6a" ); @@ -54,7 +51,7 @@ public void shouldUseFactoryMethodWithFirstParamsOfMappingMethod() { assertThat( bar6.getSomeTypeProp0() ).isEqualTo( "FOO6A" ); } - @Test + @ProcessorTest public void shouldUseFactoryMethodWithSecondParamsOfMappingMethod() { Foo7A foo7a = new Foo7A(); foo7a.setPropB( "foo7a" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/qualified/QualifiedFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/factories/qualified/QualifiedFactoryTest.java index 226c2ca8b9..ac84e555d3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/factories/qualified/QualifiedFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/qualified/QualifiedFactoryTest.java @@ -5,21 +5,18 @@ */ package org.mapstruct.ap.test.factories.qualified; -import static org.assertj.core.api.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; /** * @author Remo Meier */ @WithClasses( { Foo10.class, Bar10.class, TestQualifier.class, Bar10Factory.class, QualifiedFactoryTestMapper.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class QualifiedFactoryTest { - @Test + @ProcessorTest public void shouldUseFactoryWithoutQualifier() { Foo10 foo10 = new Foo10(); foo10.setProp( "foo10" ); @@ -31,7 +28,7 @@ public void shouldUseFactoryWithoutQualifier() { assertThat( bar10.getSomeTypeProp() ).isEqualTo( "foo10" ); } - @Test + @ProcessorTest public void shouldUseFactoryWithQualifier() { Foo10 foo10 = new Foo10(); foo10.setProp( "foo10" ); @@ -43,7 +40,7 @@ public void shouldUseFactoryWithQualifier() { assertThat( bar10.getSomeTypeProp() ).isEqualTo( "foo10" ); } - @Test + @ProcessorTest public void shouldUseFactoryWithQualifierName() { Foo10 foo10 = new Foo10(); foo10.setProp( "foo10" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/targettype/ProductTypeFactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/factories/targettype/ProductTypeFactoryTest.java index 0d39e433c0..ce140ee93c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/factories/targettype/ProductTypeFactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/targettype/ProductTypeFactoryTest.java @@ -5,22 +5,19 @@ */ package org.mapstruct.ap.test.factories.targettype; -import static org.assertj.core.api.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; /** * @author Remo Meier */ @WithClasses( { Foo9Base.class, Foo9Child.class, Bar9Base.class, Bar9Child.class, Bar9Factory.class, TargetTypeFactoryTestMapper.class } ) -@RunWith( AnnotationProcessorTestRunner.class ) public class ProductTypeFactoryTest { - @Test + @ProcessorTest public void shouldUseFactoryTwoCreateBaseClassDueToTargetType() { Foo9Base foo9 = new Foo9Base(); foo9.setProp( "foo9" ); @@ -32,7 +29,7 @@ public void shouldUseFactoryTwoCreateBaseClassDueToTargetType() { assertThat( bar9.getSomeTypeProp() ).isEqualTo( "FOO9" ); } - @Test + @ProcessorTest public void shouldUseFactoryTwoCreateChildClassDueToTargetType() { Foo9Child foo9 = new Foo9Child(); foo9.setProp( "foo9" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/fields/FieldsMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/fields/FieldsMappingTest.java index 5780e35132..8e594969fc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/fields/FieldsMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/fields/FieldsMappingTest.java @@ -5,31 +5,28 @@ */ package org.mapstruct.ap.test.fields; -import static org.assertj.core.api.Assertions.assertThat; - import org.assertj.core.util.Lists; -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; /** * @author Filip Hrisafov */ -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey( "557" ) @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) public class FieldsMappingTest { - @Test - public void shouldMapSourceToTarget() throws Exception { + @ProcessorTest + public void shouldMapSourceToTarget() { Source source = new Source(); source.normalInt = 4; source.normalList = Lists.newArrayList( 10, 11, 12 ); source.fieldOnlyWithGetter = 20; - Target target = SourceTargetMapper.INSTANCE.toSource( source ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); assertThat( target ).isNotNull(); assertThat( target.finalInt ).isEqualTo( "10" ); @@ -41,8 +38,8 @@ public void shouldMapSourceToTarget() throws Exception { assertThat( target.fieldWithMethods ).isEqualTo( "4111" ); } - @Test - public void shouldMapTargetToSource() throws Exception { + @ProcessorTest + public void shouldMapTargetToSource() { Target target = new Target(); target.finalInt = "40"; target.normalInt = "4"; diff --git a/processor/src/test/java/org/mapstruct/ap/test/fields/Source.java b/processor/src/test/java/org/mapstruct/ap/test/fields/Source.java index 1592b9e849..9e8137aee8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/fields/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/fields/Source.java @@ -22,7 +22,7 @@ public class Source { public Integer fieldOnlyWithGetter; // CHECKSTYLE:ON - private final List privateFinalList = new ArrayList( Arrays.asList( 3, 4, 5 ) ); + private final List privateFinalList = new ArrayList<>( Arrays.asList( 3, 4, 5 ) ); public List getPrivateFinalList() { return privateFinalList; diff --git a/processor/src/test/java/org/mapstruct/ap/test/fields/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/fields/SourceTargetMapper.java index a872c25feb..0716c380be 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/fields/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/fields/SourceTargetMapper.java @@ -18,8 +18,8 @@ public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - @Mapping(source = "fieldOnlyWithGetter", target = "fieldWithMethods") - Target toSource(Source source); + @Mapping(target = "fieldWithMethods", source = "fieldOnlyWithGetter") + Target toTarget(Source source); @InheritInverseConfiguration Source toSource(Target target); diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java new file mode 100644 index 0000000000..bcb78d8dcc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -0,0 +1,385 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.frommap; + +import java.text.ParseException; +import java.time.LocalDate; +import java.time.Month; +import java.time.format.DateTimeParseException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Nested; +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.assertThatThrownBy; +import static org.assertj.core.api.Assertions.entry; + +/** + * @author Christian Kosmowski + */ +@IssueKey("1075") +class FromMapMappingTest { + + @Nested + @WithClasses({ + StringMapToBeanMapper.class + }) + class StringMapToBeanTests { + + @ProcessorTest + void fromNullMap() { + assertThat( StringMapToBeanMapper.INSTANCE.fromMap( null ) ).isNull(); + } + + @ProcessorTest + void fromEmptyMap() { + StringMapToBeanMapper.Order order = StringMapToBeanMapper.INSTANCE.fromMap( Collections.emptyMap() ); + + assertThat( order ).isNotNull(); + assertThat( order.getName() ).isNull(); + assertThat( order.getPrice() ).isEqualTo( 0.0 ); + assertThat( order.getOrderDate() ).isNull(); + assertThat( order.getShipmentDate() ).isNull(); + } + + @ProcessorTest + void fromFullMap() { + Map map = new HashMap<>(); + map.put( "name", "Jacket" ); + map.put( "price", "25.5" ); + map.put( "shipmentDate", "2021-06-15" ); + StringMapToBeanMapper.Order order = StringMapToBeanMapper.INSTANCE.fromMap( map ); + + assertThat( order ).isNotNull(); + assertThat( order.getName() ).isEqualTo( "Jacket" ); + assertThat( order.getPrice() ).isEqualTo( 25.5 ); + assertThat( order.getOrderDate() ).isNull(); + assertThat( order.getShipmentDate() ).isEqualTo( LocalDate.of( 2021, Month.JUNE, 15 ) ); + } + + @ProcessorTest + void fromMapWithEmptyValuesForString() { + Map map = Collections.singletonMap( "name", "" ); + StringMapToBeanMapper.Order order = StringMapToBeanMapper.INSTANCE.fromMap( map ); + + assertThat( order ).isNotNull(); + assertThat( order.getName() ).isEqualTo( "" ); + assertThat( order.getPrice() ).isEqualTo( 0 ); + assertThat( order.getOrderDate() ).isNull(); + assertThat( order.getShipmentDate() ).isNull(); + } + + @ProcessorTest + void fromMapWithEmptyValuesForDouble() { + Map map = Collections.singletonMap( "price", "" ); + assertThatThrownBy( () -> StringMapToBeanMapper.INSTANCE.fromMap( map ) ) + .isInstanceOf( NumberFormatException.class ); + } + + @ProcessorTest + void fromMapWithEmptyValuesForDate() { + Map map = Collections.singletonMap( "orderDate", "" ); + assertThatThrownBy( () -> StringMapToBeanMapper.INSTANCE.fromMap( map ) ) + .isInstanceOf( RuntimeException.class ) + .getCause() + .isInstanceOf( ParseException.class ); + } + + @ProcessorTest + void fromMapWithEmptyValuesForLocalDate() { + Map map = Collections.singletonMap( "shipmentDate", "" ); + assertThatThrownBy( () -> StringMapToBeanMapper.INSTANCE.fromMap( map ) ) + .isInstanceOf( DateTimeParseException.class ); + } + } + + @Nested + @WithClasses({ + StringMapToBeanWithCustomPresenceCheckMapper.class + }) + class StringMapToBeanWithCustomPresenceCheckTests { + + @ProcessorTest + void fromFullMap() { + Map map = new HashMap<>(); + map.put( "name", "Jacket" ); + map.put( "price", "25.5" ); + map.put( "shipmentDate", "2021-06-15" ); + StringMapToBeanWithCustomPresenceCheckMapper.Order order = + StringMapToBeanWithCustomPresenceCheckMapper.INSTANCE.fromMap( map ); + + assertThat( order ).isNotNull(); + assertThat( order.getName() ).isEqualTo( "Jacket" ); + assertThat( order.getPrice() ).isEqualTo( 25.5 ); + assertThat( order.getOrderDate() ).isNull(); + assertThat( order.getShipmentDate() ).isEqualTo( LocalDate.of( 2021, Month.JUNE, 15 ) ); + } + + @ProcessorTest + void fromMapWithEmptyValuesForString() { + Map map = new HashMap<>(); + map.put( "name", "" ); + map.put( "price", "" ); + map.put( "orderDate", "" ); + map.put( "shipmentDate", "" ); + StringMapToBeanWithCustomPresenceCheckMapper.Order order = + StringMapToBeanWithCustomPresenceCheckMapper.INSTANCE.fromMap( map ); + + assertThat( order ).isNotNull(); + assertThat( order.getName() ).isNull(); + assertThat( order.getPrice() ).isEqualTo( 0 ); + assertThat( order.getOrderDate() ).isNull(); + assertThat( order.getShipmentDate() ).isNull(); + } + + } + + @ProcessorTest + @WithClasses(MapToBeanDefinedMapper.class) + void shouldMapWithDefinedMapping() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "number", 44 ); + + MapToBeanDefinedMapper.Target target = MapToBeanDefinedMapper.INSTANCE.toTarget( sourceMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getNormalInt() ).isEqualTo( "44" ); + } + + @ProcessorTest + @WithClasses(MapToBeanImplicitMapper.class) + void shouldMapWithImpicitMapping() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "name", "mapstruct" ); + + MapToBeanImplicitMapper.Target target = MapToBeanImplicitMapper.INSTANCE.toTarget( sourceMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isEqualTo( "mapstruct" ); + } + + @ProcessorTest + @WithClasses(MapToBeanUpdateImplicitMapper.class) + void shouldMapToExistingTargetWithImplicitMapping() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "rating", 5 ); + + MapToBeanUpdateImplicitMapper.Target existingTarget = new MapToBeanUpdateImplicitMapper.Target(); + existingTarget.setRating( 4 ); + existingTarget.setName( "mapstruct" ); + + MapToBeanUpdateImplicitMapper.Target target = MapToBeanUpdateImplicitMapper.INSTANCE + .toTarget( existingTarget, sourceMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isEqualTo( "mapstruct" ); + assertThat( target.getRating() ).isEqualTo( 5 ); + } + + @ProcessorTest + @WithClasses(MapToBeanWithDefaultMapper.class) + void shouldMapWithDefaultValue() { + Map sourceMap = new HashMap<>(); + + MapToBeanWithDefaultMapper.Target target = MapToBeanWithDefaultMapper.INSTANCE + .toTarget( sourceMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getNormalInt() ).isEqualTo( "4711" ); + } + + @ProcessorTest + @WithClasses(MapToBeanUsingMappingMethodMapper.class) + void shouldMapUsingMappingMethod() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "number", 23 ); + + MapToBeanUsingMappingMethodMapper.Target target = MapToBeanUsingMappingMethodMapper.INSTANCE + .toTarget( sourceMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getNormalInt() ).isEqualTo( "converted_23" ); + } + + @ProcessorTest + @WithClasses(MapToBeanFromMultipleSources.class) + void shouldMapFromMultipleSources() { + Map integers = new HashMap<>(); + integers.put( "number", 23 ); + + Map strings = new HashMap<>(); + strings.put( "string", "stringFromMap" ); + + MapToBeanFromMultipleSources.Source source = new MapToBeanFromMultipleSources.Source(); + + MapToBeanFromMultipleSources.Target target = MapToBeanFromMultipleSources.INSTANCE + .toTarget( integers, strings, source ); + + assertThat( target ).isNotNull(); + assertThat( target.getInteger() ).isEqualTo( 23 ); + assertThat( target.getString() ).isEqualTo( "stringFromMap" ); + assertThat( target.getStringFromBean() ).isEqualTo( "stringFromBean" ); + } + + @ProcessorTest + @WithClasses(MapToBeanFromMapAndNestedSource.class) + void shouldMapFromNestedSource() { + Map integers = new HashMap<>(); + integers.put( "number", 23 ); + + MapToBeanFromMapAndNestedSource.Source source = new MapToBeanFromMapAndNestedSource.Source(); + + MapToBeanFromMapAndNestedSource.Target target = MapToBeanFromMapAndNestedSource.INSTANCE + .toTarget( integers, source ); + + assertThat( target ).isNotNull(); + assertThat( target.getInteger() ).isEqualTo( 23 ); + assertThat( target.getStringFromNestedSource() ).isEqualTo( "nestedString" ); + } + + @ProcessorTest + @WithClasses(MapToBeanFromMapAndNestedMap.class) + void shouldMapFromNestedMap() { + + MapToBeanFromMapAndNestedMap.Source source = new MapToBeanFromMapAndNestedMap.Source(); + MapToBeanFromMapAndNestedMap.Target target = MapToBeanFromMapAndNestedMap.INSTANCE + .toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNestedTarget() ).isNotNull(); + assertThat( target.getNestedTarget().getStringFromNestedMap() ).isEqualTo( "valueFromNestedMap" ); + } + + @IssueKey("2553") + @ProcessorTest + @WithClasses(MapToBeanFromMapAndNestedMapWithDefinedMapping.class) + void shouldMapFromNestedMapWithDefinedMapping() { + + MapToBeanFromMapAndNestedMapWithDefinedMapping.Source source = + new MapToBeanFromMapAndNestedMapWithDefinedMapping.Source(); + MapToBeanFromMapAndNestedMapWithDefinedMapping.Target target = + MapToBeanFromMapAndNestedMapWithDefinedMapping.INSTANCE.toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNested() ).isEqualTo( "valueFromNestedMap" ); + } + + @ProcessorTest + @WithClasses(ObjectMapToBeanWithQualifierMapper.class) + void shouldUseObjectQualifiedMethod() { + Map vehicles = new HashMap<>(); + vehicles.put( "car", new ObjectMapToBeanWithQualifierMapper.Car( "Tesla" ) ); + + ObjectMapToBeanWithQualifierMapper.VehiclesDto dto = + ObjectMapToBeanWithQualifierMapper.INSTANCE.map( vehicles ); + + assertThat( dto.getCar() ).isNotNull(); + assertThat( dto.getCar() ).isInstanceOf( ObjectMapToBeanWithQualifierMapper.CarDto.class ); + assertThat( dto.getCar().getBrand() ).isEqualTo( "Tesla" ); + } + + @ProcessorTest + @WithClasses(MapToBeanTypeCheckMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + type = MapToBeanTypeCheckMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "Unmapped target property: \"value\"." + ), + @Diagnostic( + type = MapToBeanTypeCheckMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "The Map parameter \"source\" cannot be used for property mapping. " + + "It must be typed with Map but it was typed with Map." + ), + } + ) + void shouldWarnAboutWrongMapTypes() { + + Map upsideDownMap = new HashMap<>(); + upsideDownMap.put( 23, "number" ); + + MapToBeanTypeCheckMapper.Target target = MapToBeanTypeCheckMapper.INSTANCE + .toTarget( upsideDownMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + } + + @ProcessorTest + @WithClasses(MapToBeanRawMapMapper.class) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + type = MapToBeanRawMapMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "Unmapped target property: \"value\"." + ), + @Diagnostic( + type = MapToBeanRawMapMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 21, + message = "The Map parameter \"source\" cannot be used for property mapping. " + + "It must be typed with Map but it was raw." + ), + } + ) + void shouldWarnAboutRawMapTypes() { + + Map rawMap = new HashMap<>(); + rawMap.put( "value", "number" ); + + MapToBeanRawMapMapper.Target target = MapToBeanRawMapMapper.INSTANCE + .toTarget( rawMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + MapToBeanNonStringMapAsMultiSourceMapper.class + }) + void shouldNotWarnIfMappedIsUsedAsSourceParameter() { + MapToBeanNonStringMapAsMultiSourceMapper.Target target = MapToBeanNonStringMapAsMultiSourceMapper.INSTANCE + .toTarget( + new MapToBeanNonStringMapAsMultiSourceMapper.Source( "test" ), + Collections.singletonMap( 10, "value" ) + ); + + assertThat( target.getValue() ).isEqualTo( "test" ); + assertThat( target.getMap() ) + .containsOnly( entry( "10", "value" ) ); + } + + @ProcessorTest + @WithClasses(MapToBeanImplicitUnmappedSourcePolicyMapper.class) + void shouldNotReportUnmappedSourcePropertiesWithMap() { + Map sourceMap = new HashMap<>(); + sourceMap.put( "name", "mapstruct" ); + + MapToBeanImplicitUnmappedSourcePolicyMapper.Target target = + MapToBeanImplicitUnmappedSourcePolicyMapper.INSTANCE.toTarget( sourceMap ); + + assertThat( target ).isNotNull(); + assertThat( target.getName() ).isEqualTo( "mapstruct" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanDefinedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanDefinedMapper.java new file mode 100644 index 0000000000..eef8c285b3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanDefinedMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanDefinedMapper { + + MapToBeanDefinedMapper INSTANCE = Mappers.getMapper( MapToBeanDefinedMapper.class ); + + @Mapping(target = "normalInt", source = "number") + Target toTarget(Map source); + + class Target { + + private String normalInt; + + public String getNormalInt() { + return normalInt; + } + + public void setNormalInt(String normalInt) { + this.normalInt = normalInt; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.java new file mode 100644 index 0000000000..f1b21263a4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMap.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.frommap; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanFromMapAndNestedMap { + + MapToBeanFromMapAndNestedMap INSTANCE = Mappers.getMapper( MapToBeanFromMapAndNestedMap.class ); + + Target toTarget(Source source); + + class Source { + + private Map nestedTarget = new HashMap<>( ); + + public Map getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(Map nestedTarget) { + this.nestedTarget = nestedTarget; + } + + public Source() { + nestedTarget.put( "stringFromNestedMap", "valueFromNestedMap" ); + } + } + + class Target { + + private NestedTarget nestedTarget; + + public NestedTarget getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(NestedTarget nestedTarget) { + this.nestedTarget = nestedTarget; + } + + } + + class NestedTarget { + private String stringFromNestedMap; + + public String getStringFromNestedMap() { + return stringFromNestedMap; + } + + public void setStringFromNestedMap(String stringFromNestedMap) { + this.stringFromNestedMap = stringFromNestedMap; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java new file mode 100644 index 0000000000..2203836ea6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.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.frommap; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MapToBeanFromMapAndNestedMapWithDefinedMapping { + + MapToBeanFromMapAndNestedMapWithDefinedMapping INSTANCE = Mappers.getMapper( + MapToBeanFromMapAndNestedMapWithDefinedMapping.class ); + + @Mapping(target = "nested", source = "nestedTarget.nested") + Target toTarget(Source source); + + class Source { + + private Map nestedTarget = new HashMap<>(); + + public Map getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(Map nestedTarget) { + this.nestedTarget = nestedTarget; + } + + public Source() { + nestedTarget.put( "nested", "valueFromNestedMap" ); + } + } + + class Target { + + private String nested; + + public String getNested() { + return nested; + } + + public void setNested(String nested) { + this.nested = nested; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedSource.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedSource.java new file mode 100644 index 0000000000..c66080332e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedSource.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanFromMapAndNestedSource { + + MapToBeanFromMapAndNestedSource INSTANCE = Mappers.getMapper( MapToBeanFromMapAndNestedSource.class ); + + @Mapping(target = "integer", source = "integers.number") + @Mapping(target = "stringFromNestedSource", source = "source.nestedSource.nestedString") + Target toTarget(Map integers, Source source); + + class Source { + + private String stringFromBean = "stringFromBean"; + private NestedSource nestedSource = new NestedSource(); + + public String getStringFromBean() { + return stringFromBean; + } + + public void setStringFromBean(String stringFromBean) { + this.stringFromBean = stringFromBean; + } + + public NestedSource getNestedSource() { + return nestedSource; + } + + public void setNestedSource(NestedSource nestedSource) { + this.nestedSource = nestedSource; + } + + class NestedSource { + + private String nestedString = "nestedString"; + + public String getNestedString() { + return nestedString; + } + } + } + + class Target { + + private int integer; + private String stringFromNestedSource; + + public int getInteger() { + return integer; + } + + public void setInteger(int integer) { + this.integer = integer; + } + + public String getStringFromNestedSource() { + return stringFromNestedSource; + } + + public void setStringFromNestedSource(String stringFromNestedSource) { + this.stringFromNestedSource = stringFromNestedSource; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMultipleSources.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMultipleSources.java new file mode 100644 index 0000000000..c5c9b2e665 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMultipleSources.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanFromMultipleSources { + + MapToBeanFromMultipleSources INSTANCE = Mappers.getMapper( MapToBeanFromMultipleSources.class ); + + @Mapping(target = "integer", source = "integers.number") + @Mapping(target = "string", source = "strings.string") + @Mapping(target = "stringFromBean", source = "bean.stringFromBean") + Target toTarget(Map integers, Map strings, Source bean); + + class Source { + private String stringFromBean = "stringFromBean"; + + public String getStringFromBean() { + return stringFromBean; + } + + public void setStringFromBean(String stringFromBean) { + this.stringFromBean = stringFromBean; + } + } + + class Target { + + private int integer; + private String string; + private String stringFromBean; + + public int getInteger() { + return integer; + } + + public void setInteger(int integer) { + this.integer = integer; + } + + public String getString() { + return string; + } + + public void setString(String string) { + this.string = string; + } + + public String getStringFromBean() { + return stringFromBean; + } + + public void setStringFromBean(String stringFromBean) { + this.stringFromBean = stringFromBean; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitMapper.java new file mode 100644 index 0000000000..ca4b7a3d21 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanImplicitMapper { + + MapToBeanImplicitMapper INSTANCE = Mappers.getMapper( MapToBeanImplicitMapper.class ); + + Target toTarget(Map source); + + 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/frommap/MapToBeanImplicitUnmappedSourcePolicyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitUnmappedSourcePolicyMapper.java new file mode 100644 index 0000000000..2c72dc7b70 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanImplicitUnmappedSourcePolicyMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface MapToBeanImplicitUnmappedSourcePolicyMapper { + + MapToBeanImplicitUnmappedSourcePolicyMapper INSTANCE = + Mappers.getMapper( MapToBeanImplicitUnmappedSourcePolicyMapper.class ); + + Target toTarget(Map source); + + 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/frommap/MapToBeanNonStringMapAsMultiSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanNonStringMapAsMultiSourceMapper.java new file mode 100644 index 0000000000..2606a9a037 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanNonStringMapAsMultiSourceMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanNonStringMapAsMultiSourceMapper { + + MapToBeanNonStringMapAsMultiSourceMapper INSTANCE = + Mappers.getMapper( MapToBeanNonStringMapAsMultiSourceMapper.class ); + + Target toTarget(Source source, Map map); + + class Source { + private final String value; + + public Source(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + class Target { + + private final String value; + private final Map map; + + public Target(String value, Map map) { + this.value = value; + this.map = map; + } + + public String getValue() { + return value; + } + + public Map getMap() { + return map; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanRawMapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanRawMapMapper.java new file mode 100644 index 0000000000..40215ab183 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanRawMapMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanRawMapMapper { + + MapToBeanRawMapMapper INSTANCE = Mappers.getMapper( MapToBeanRawMapMapper.class ); + + Target toTarget(Map source); + + 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/frommap/MapToBeanTypeCheckMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanTypeCheckMapper.java new file mode 100644 index 0000000000..346c153d71 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanTypeCheckMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanTypeCheckMapper { + + MapToBeanTypeCheckMapper INSTANCE = Mappers.getMapper( MapToBeanTypeCheckMapper.class ); + + Target toTarget(Map source); + + 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/frommap/MapToBeanUpdateImplicitMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUpdateImplicitMapper.java new file mode 100644 index 0000000000..82fe906bfe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUpdateImplicitMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +public interface MapToBeanUpdateImplicitMapper { + + MapToBeanUpdateImplicitMapper INSTANCE = Mappers.getMapper( MapToBeanUpdateImplicitMapper.class ); + + Target toTarget(@MappingTarget Target target, Map source); + + class Target { + + private String name; + private Integer rating; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getRating() { + return rating; + } + + public void setRating(Integer rating) { + this.rating = rating; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java new file mode 100644 index 0000000000..225ca00adc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanUsingMappingMethodMapper { + + MapToBeanUsingMappingMethodMapper INSTANCE = Mappers.getMapper( MapToBeanUsingMappingMethodMapper.class ); + + @Mapping(target = "normalInt", source = "source.number") + Target toTarget(Map source); + + default String mapIntegerToString( Integer input ) { + return "converted_" + input; + } + + class Target { + + private String normalInt; + + public String getNormalInt() { + return normalInt; + } + + public void setNormalInt(String normalInt) { + this.normalInt = normalInt; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanWithDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanWithDefaultMapper.java new file mode 100644 index 0000000000..104d34bd0a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanWithDefaultMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Christian Kosmowski + */ +@Mapper +public interface MapToBeanWithDefaultMapper { + + MapToBeanWithDefaultMapper INSTANCE = Mappers.getMapper( MapToBeanWithDefaultMapper.class ); + + @Mapping(target = "normalInt", source = "number", defaultValue = "4711") + Target toTarget(Map source); + + class Target { + + private String normalInt; + + public String getNormalInt() { + return normalInt; + } + + public void setNormalInt(String normalInt) { + this.normalInt = normalInt; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/ObjectMapToBeanWithQualifierMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/ObjectMapToBeanWithQualifierMapper.java new file mode 100644 index 0000000000..54ddb0e42a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/ObjectMapToBeanWithQualifierMapper.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.frommap; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface ObjectMapToBeanWithQualifierMapper { + + ObjectMapToBeanWithQualifierMapper INSTANCE = Mappers.getMapper( ObjectMapToBeanWithQualifierMapper.class ); + + @Mapping( target = "car", qualifiedByName = "objectToCar") + VehiclesDto map(Map vehicles); + + @Named("objectToCar") + default CarDto objectToCar(Object object) { + CarDto car = new CarDto(); + + if ( object instanceof Car ) { + car.setBrand( ( (Car) object ).brand ); + } + + return car; + } + + class VehiclesDto { + + private VehicleDto car; + + public VehicleDto getCar() { + return car; + } + + public void setCar(VehicleDto car) { + this.car = car; + } + } + + class VehicleDto { + private String brand; + + public String getBrand() { + return brand; + } + + public void setBrand(String brand) { + this.brand = brand; + } + } + + class CarDto extends VehicleDto { + + } + + class Car { + + private final String brand; + + public Car(String brand) { + this.brand = brand; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/StringMapToBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/StringMapToBeanMapper.java new file mode 100644 index 0000000000..95b0f72597 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/StringMapToBeanMapper.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.frommap; + +import java.time.LocalDate; +import java.util.Date; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface StringMapToBeanMapper { + StringMapToBeanMapper INSTANCE = Mappers.getMapper( StringMapToBeanMapper.class ); + + Order fromMap(Map map); + + class Order { + private String name; + private double price; + private Date orderDate; + private LocalDate shipmentDate; + + public Order() { + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return this.price; + } + + public void setPrice(double price) { + this.price = price; + } + + public Date getOrderDate() { + return this.orderDate; + } + + public void setOrderDate(Date orderDate) { + this.orderDate = orderDate; + } + + public LocalDate getShipmentDate() { + return this.shipmentDate; + } + + public void setShipmentDate(LocalDate shipmentDate) { + this.shipmentDate = shipmentDate; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/StringMapToBeanWithCustomPresenceCheckMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/StringMapToBeanWithCustomPresenceCheckMapper.java new file mode 100644 index 0000000000..62c105599f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/StringMapToBeanWithCustomPresenceCheckMapper.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.frommap; + +import java.time.LocalDate; +import java.util.Date; +import java.util.Map; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface StringMapToBeanWithCustomPresenceCheckMapper { + StringMapToBeanWithCustomPresenceCheckMapper INSTANCE = + Mappers.getMapper( StringMapToBeanWithCustomPresenceCheckMapper.class ); + + Order fromMap(Map map); + + @Condition + default boolean isNotEmpty(String value) { + return value != null && !value.isEmpty(); + } + + class Order { + private String name; + private double price; + private Date orderDate; + private LocalDate shipmentDate; + + public Order() { + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public double getPrice() { + return this.price; + } + + public void setPrice(double price) { + this.price = price; + } + + public Date getOrderDate() { + return this.orderDate; + } + + public void setOrderDate(Date orderDate) { + this.orderDate = orderDate; + } + + public LocalDate getShipmentDate() { + return this.shipmentDate; + } + + public void setShipmentDate(LocalDate shipmentDate) { + this.shipmentDate = shipmentDate; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java b/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.java new file mode 100644 index 0000000000..25489a5b6b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/gem/ConstantTest.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.gem; + +import org.junit.jupiter.api.Test; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.internal.gem.MappingConstantsGem; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test constants values + * + * @author Sjaak Derksen + */ +public class ConstantTest { + + @Test + public void constantsShouldBeEqual() { + assertThat( MappingConstants.ANY_REMAINING ).isEqualTo( MappingConstantsGem.ANY_REMAINING ); + assertThat( MappingConstants.ANY_UNMAPPED ).isEqualTo( MappingConstantsGem.ANY_UNMAPPED ); + assertThat( MappingConstants.NULL ).isEqualTo( MappingConstantsGem.NULL ); + assertThat( MappingConstants.THROW_EXCEPTION ).isEqualTo( MappingConstantsGem.THROW_EXCEPTION ); + assertThat( MappingConstants.SUFFIX_TRANSFORMATION ).isEqualTo( MappingConstantsGem.SUFFIX_TRANSFORMATION ); + assertThat( MappingConstants.STRIP_SUFFIX_TRANSFORMATION ) + .isEqualTo( MappingConstantsGem.STRIP_SUFFIX_TRANSFORMATION ); + assertThat( MappingConstants.PREFIX_TRANSFORMATION ).isEqualTo( MappingConstantsGem.PREFIX_TRANSFORMATION ); + assertThat( MappingConstants.STRIP_PREFIX_TRANSFORMATION ) + .isEqualTo( MappingConstantsGem.STRIP_PREFIX_TRANSFORMATION ); + assertThat( MappingConstants.CASE_TRANSFORMATION ).isEqualTo( MappingConstantsGem.CASE_TRANSFORMATION ); + } + + @Test + public void componentModelConstantsShouldBeEqual() { + assertThat( MappingConstants.ComponentModel.DEFAULT ) + .isEqualTo( MappingConstantsGem.ComponentModelGem.DEFAULT ); + assertThat( MappingConstants.ComponentModel.CDI ).isEqualTo( MappingConstantsGem.ComponentModelGem.CDI ); + assertThat( MappingConstants.ComponentModel.SPRING ).isEqualTo( MappingConstantsGem.ComponentModelGem.SPRING ); + assertThat( MappingConstants.ComponentModel.JSR330 ).isEqualTo( MappingConstantsGem.ComponentModelGem.JSR330 ); + assertThat( MappingConstants.ComponentModel.JAKARTA ) + .isEqualTo( MappingConstantsGem.ComponentModelGem.JAKARTA ); + assertThat( MappingConstants.ComponentModel.JAKARTA_CDI ) + .isEqualTo( MappingConstantsGem.ComponentModelGem.JAKARTA_CDI ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java new file mode 100644 index 0000000000..f460180651 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.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.gem; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.mapstruct.CollectionMappingStrategy; +import org.mapstruct.ConditionStrategy; +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MappingInheritanceStrategy; +import org.mapstruct.NullValueCheckStrategy; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; +import org.mapstruct.ap.internal.gem.ConditionStrategyGem; +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.ReportingPolicyGem; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test for manually created gems on enumeration types + * + * @author Andreas Gudian + */ +public class EnumGemsTest { + @Test + public void collectionMappingStrategyGemIsCorrect() { + assertThat( namesOf( CollectionMappingStrategy.values() ) ).isEqualTo( + namesOf( CollectionMappingStrategyGem.values() ) ); + } + + @Test + public void mappingInheritanceStrategyGemIsCorrect() { + assertThat( namesOf( MappingInheritanceStrategy.values() ) ).isEqualTo( + namesOf( MappingInheritanceStrategyGem.values() ) ); + } + + @Test + public void nullValueCheckStrategyGemIsCorrect() { + assertThat( namesOf( NullValueCheckStrategy.values() ) ).isEqualTo( + namesOf( NullValueCheckStrategyGem.values() ) ); + } + + @Test + public void nullValueMappingStrategyGemIsCorrect() { + assertThat( namesOf( NullValueMappingStrategy.values() ) ).isEqualTo( + namesOf( NullValueMappingStrategyGem.values() ) ); + } + + @Test + public void reportingPolicyGemIsCorrect() { + assertThat( namesOf( ReportingPolicy.values() ) ).isEqualTo( + namesOf( ReportingPolicyGem.values() ) ); + } + + @Test + public void injectionStrategyGemIsCorrect() { + assertThat( namesOf( InjectionStrategy.values() ) ).isEqualTo( + namesOf( InjectionStrategyGem.values() ) ); + } + + @Test + public void conditionStrategyGemIsCorrect() { + assertThat( namesOf( ConditionStrategy.values() ) ).isEqualTo( + namesOf( ConditionStrategyGem.values() ) ); + } + + private static List namesOf(Enum[] values) { + return Stream.of( values ) + .map( Enum::name ) + .collect( Collectors.toList() ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/generics/AbstractTo.java b/processor/src/test/java/org/mapstruct/ap/test/generics/AbstractTo.java index 5d7f0ba82f..c0f4c423ef 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/generics/AbstractTo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/generics/AbstractTo.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.generics; - /** * @author Andreas Gudian */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/generics/GenericsTest.java b/processor/src/test/java/org/mapstruct/ap/test/generics/GenericsTest.java index 3f1ca3a818..091c59f78d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/generics/GenericsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/generics/GenericsTest.java @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.generics; -import static org.assertj.core.api.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; /** * @author Andreas Gudian @@ -24,10 +22,9 @@ SourceTargetMapper.class, TargetTo.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class GenericsTest { - @Test + @ProcessorTest @IssueKey("574") public void mapsIdCorrectly() { TargetTo target = new TargetTo(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/generics/TargetTo.java b/processor/src/test/java/org/mapstruct/ap/test/generics/TargetTo.java index 799dc2509e..cac761f14b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/generics/TargetTo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/generics/TargetTo.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.generics; - /** * @author Andreas Gudian * diff --git a/processor/src/test/java/org/mapstruct/ap/test/generics/genericsupertype/MapperWithGenericSuperClassTest.java b/processor/src/test/java/org/mapstruct/ap/test/generics/genericsupertype/MapperWithGenericSuperClassTest.java index 9d53fa0f94..4caa6f5712 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/generics/genericsupertype/MapperWithGenericSuperClassTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/generics/genericsupertype/MapperWithGenericSuperClassTest.java @@ -5,14 +5,12 @@ */ package org.mapstruct.ap.test.generics.genericsupertype; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; -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; /** * @author Gunnar Morling @@ -24,15 +22,14 @@ MapperBase.class, VesselSearchResultMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class MapperWithGenericSuperClassTest { - @Test + @ProcessorTest public void canCreateImplementationForMapperWithGenericSuperClass() { Vessel vessel = new Vessel(); vessel.setName( "Pacific Queen" ); - SearchResult vessels = new SearchResult(); + SearchResult vessels = new SearchResult<>(); vessels.setValues( Arrays.asList( vessel ) ); vessels.setSize( 1L ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignore/IgnorePropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignore/IgnorePropertyTest.java index 3b2496327d..3bafb776a3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/ignore/IgnorePropertyTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/ignore/IgnorePropertyTest.java @@ -5,18 +5,16 @@ */ package org.mapstruct.ap.test.ignore; -import static org.assertj.core.api.Assertions.assertThat; - 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 ignoring properties during the mapping. @@ -24,10 +22,9 @@ * @author Gunnar Morling */ @WithClasses({ Animal.class, AnimalDto.class, AnimalMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class IgnorePropertyTest { - @Test + @ProcessorTest @IssueKey("72") public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { Animal animal = new Animal( "Bruno", 100, 23, "black" ); @@ -43,7 +40,7 @@ public void shouldNotPropagateIgnoredPropertyGivenViaTargetAttribute() { assertThat( animalDto.publicColor ).isNull(); } - @Test + @ProcessorTest @IssueKey("1392") public void shouldIgnoreAllTargetPropertiesWithNoUnmappedTargetWarnings() { Animal animal = new Animal( "Bruno", 100, 23, "black" ); @@ -59,7 +56,7 @@ public void shouldIgnoreAllTargetPropertiesWithNoUnmappedTargetWarnings() { assertThat( animalDto.publicColor ).isNull(); } - @Test + @ProcessorTest @IssueKey("337") public void propertyIsIgnoredInReverseMappingWhenSourceIsAlsoSpecifiedICWIgnore() { AnimalDto animalDto = new AnimalDto( "Bruno", 100, 23, "black" ); @@ -73,7 +70,7 @@ public void propertyIsIgnoredInReverseMappingWhenSourceIsAlsoSpecifiedICWIgnore( assertThat( animal.publicColour ).isNull(); } - @Test + @ProcessorTest @IssueKey("833") @WithClasses({Preditor.class, PreditorDto.class, ErroneousTargetHasNoWriteAccessorMapper.class}) @ExpectedCompilationOutcome( @@ -82,8 +79,7 @@ public void propertyIsIgnoredInReverseMappingWhenSourceIsAlsoSpecifiedICWIgnore( @Diagnostic(type = ErroneousTargetHasNoWriteAccessorMapper.class, kind = Kind.ERROR, line = 22, - messageRegExp = "Property \"hasClaws\" has no write accessor in " + - "org.mapstruct.ap.test.ignore.PreditorDto\\.") + message = "Property \"hasClaws\" has no write accessor in PreditorDto.") } ) public void shouldGiveErrorOnMappingForReadOnlyProp() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignore/expand/IgnorePropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignore/expand/IgnorePropertyTest.java index f3dc994a6e..deaa75bbe7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/ignore/expand/IgnorePropertyTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/ignore/expand/IgnorePropertyTest.java @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.ignore.expand; -import static org.assertj.core.api.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; /** * Test for ignoring properties during the mapping. @@ -25,10 +23,9 @@ FlattenedToolBox.class, ToolBoxMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class IgnorePropertyTest { - @Test + @ProcessorTest @IssueKey("1392") public void shouldIgnoreAll() { FlattenedToolBox toolboxSource = new FlattenedToolBox(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignore/inherit/IgnorePropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignore/inherit/IgnorePropertyTest.java index d0f829fa4f..7ff4ea386d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/ignore/inherit/IgnorePropertyTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/ignore/inherit/IgnorePropertyTest.java @@ -7,13 +7,11 @@ import java.util.Date; -import static org.assertj.core.api.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; /** * Test for ignoring properties during the mapping. @@ -30,10 +28,9 @@ WorkBenchEntity.class, ToolMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class IgnorePropertyTest { - @Test + @ProcessorTest @IssueKey("1392") /** * Should not issue warnings on unmapped target properties @@ -54,9 +51,9 @@ public void shouldIgnoreAllExeptOveriddenInherited() { } - @Test - @IssueKey("1392") - public void shouldIgnoreBase() { + @ProcessorTest + @IssueKey("1933") + public void shouldInheritIgnoreByDefaultFromBase() { WorkBenchDto workBenchDto = new WorkBenchDto(); workBenchDto.setArticleName( "MyBench" ); @@ -66,6 +63,26 @@ public void shouldIgnoreBase() { WorkBenchEntity benchTarget = ToolMapper.INSTANCE.mapBench( workBenchDto ); + assertThat( benchTarget ).isNotNull(); + assertThat( benchTarget.getArticleName() ).isNull(); + assertThat( benchTarget.getDescription() ).isEqualTo( "Beautiful" ); + assertThat( benchTarget.getKey() ).isNull(); + assertThat( benchTarget.getModificationDate() ).isNull(); + assertThat( benchTarget.getCreationDate() ).isNull(); + } + + @ProcessorTest + @IssueKey("1933") + public void shouldOnlyIgnoreBase() { + + WorkBenchDto workBenchDto = new WorkBenchDto(); + workBenchDto.setArticleName( "MyBench" ); + workBenchDto.setArticleDescription( "Beautiful" ); + workBenchDto.setCreationDate( new Date() ); + workBenchDto.setModificationDate( new Date() ); + + WorkBenchEntity benchTarget = ToolMapper.INSTANCE.mapBenchWithImplicit( workBenchDto ); + assertThat( benchTarget ).isNotNull(); assertThat( benchTarget.getArticleName() ).isEqualTo( "MyBench" ); assertThat( benchTarget.getDescription() ).isEqualTo( "Beautiful" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignore/inherit/ToolMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignore/inherit/ToolMapper.java index f2126a3136..10c3a513f5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/ignore/inherit/ToolMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/ignore/inherit/ToolMapper.java @@ -29,12 +29,21 @@ public interface ToolMapper { @InheritConfiguration( name = "mapBase" ) ToolEntity mapTool(ToolDto source); - // demonstrates that all the businss stuff is mapped (implicit-by-name and defined) + // demonstrates that all the business stuff is mapped only defined because BeanMapping#ignoreByDefault @InheritConfiguration( name = "mapBase" ) @Mapping(target = "description", source = "articleDescription") WorkBenchEntity mapBench(WorkBenchDto source); + // demonstrates that all the business stuff is mapped (implicit-by-name and defined) + @BeanMapping( ignoreByDefault = false ) // needed to override the one from the BeanMapping mapBase + @InheritConfiguration( name = "mapBase" ) + @Mapping(target = "description", source = "articleDescription") + WorkBenchEntity mapBenchWithImplicit(WorkBenchDto source); + // ignore all the base properties by default @BeanMapping( ignoreByDefault = true ) + @Mapping( target = "key", ignore = true) + @Mapping( target = "modificationDate", ignore = true) + @Mapping( target = "creationDate", ignore = true) BaseEntity mapBase(Object o); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapper.java new file mode 100644 index 0000000000..4ec3c64500 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapper.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.ignorebydefaultsource; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( + unmappedTargetPolicy = ReportingPolicy.IGNORE, + unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface ErroneousSourceTargetMapper { + ErroneousSourceTargetMapper INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper.class ); + + @Mapping(source = "one", target = "one") + Target sourceToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.java new file mode 100644 index 0000000000..459e2f426a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/ErroneousSourceTargetMapperWithIgnoreByDefault.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.ignorebydefaultsource; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +@Mapper( + unmappedTargetPolicy = ReportingPolicy.IGNORE, + unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface ErroneousSourceTargetMapperWithIgnoreByDefault { + ErroneousSourceTargetMapperWithIgnoreByDefault INSTANCE = Mappers.getMapper( + ErroneousSourceTargetMapperWithIgnoreByDefault.class ); + + @Mapping(source = "one", target = "one") + @BeanMapping(ignoreByDefault = true) + Target sourceToTarget(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.java new file mode 100644 index 0000000000..fce6d49648 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/IgnoreByDefaultSourcesTest.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.ignorebydefaultsource; + +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; + +@IssueKey("2560") +public class IgnoreByDefaultSourcesTest { + + @ProcessorTest + @WithClasses({ ErroneousSourceTargetMapperWithIgnoreByDefault.class, Source.class, Target.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSourceTargetMapperWithIgnoreByDefault.class, + kind = Kind.ERROR, + line = 23, + message = "Unmapped source property: \"other\".") + } + ) + public void shouldRaiseErrorDueToNonIgnoredSourcePropertyWithBeanMappingIgnoreByDefault() { + } + + @ProcessorTest + @WithClasses({ ErroneousSourceTargetMapper.class, Source.class, Target.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSourceTargetMapper.class, + kind = Kind.ERROR, + line = 20, + message = "Unmapped source property: \"other\".") + } + ) + public void shouldRaiseErrorDueToNonIgnoredSourceProperty() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Source.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Source.java new file mode 100644 index 0000000000..81d6441d57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Source.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.ignorebydefaultsource; + +class Source { + private int one; + private int other; + + public int getOne() { + return one; + } + + public void setOne(int one) { + this.one = one; + } + + public int getOther() { + return other; + } + + public void setOther(int other) { + this.other = other; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Target.java b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/Target.java new file mode 100644 index 0000000000..68ec5c8933 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignorebydefaultsource/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.ignorebydefaultsource; + +class Target { + private int one; + + public int getOne() { + return one; + } + + public void setOne(int one) { + this.one = one; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.java new file mode 100644 index 0000000000..223f99f705 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/Animal.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.ignored; + +public class Animal { + + //CHECKSTYLE:OFF + public Integer publicAge; + public String publicColour; + //CHECKSTYLE:OFN + private String colour; + private String name; + private int size; + private Integer age; + + // private String colour; + public Animal() { + } + + public Animal(String name, int size, Integer age, String colour) { + this.name = name; + this.size = size; + this.publicAge = age; + this.age = age; + this.publicColour = colour; + this.colour = colour; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getSize() { + return size; + } + + public void setSize(int size) { + this.size = size; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getColour() { + return colour; + } + + public void setColour( String colour ) { + this.colour = colour; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.java new file mode 100644 index 0000000000..1651f9b56b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalDto.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.ignored; + +public class AnimalDto { + + //CHECKSTYLE:OFF + public Integer publicAge; + public String publicColor; + //CHECKSTYLE:ON + private String name; + private Integer size; + private Integer age; + private String color; + + public AnimalDto() { + + } + + public AnimalDto(String name, Integer size, Integer age, String color) { + this.name = name; + this.size = size; + this.publicAge = age; + this.age = age; + this.publicColor = color; + this.color = color; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getSize() { + return size; + } + + public void setSize(Integer size) { + this.size = size; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.java new file mode 100644 index 0000000000..2753b36070 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/AnimalMapper.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.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface AnimalMapper { + + AnimalMapper INSTANCE = Mappers.getMapper( AnimalMapper.class ); + + @Ignored( targets = { "publicAge", "age", "publicColor", "color" } ) + AnimalDto animalToDto( Animal animal ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.java new file mode 100644 index 0000000000..b113e715aa --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ErroneousMapper.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.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ErroneousMapper { + + ErroneousMapper INSTANCE = Mappers.getMapper( ErroneousMapper.class ); + + @Mapping(target = "name", ignore = true) + @Ignored(targets = { "name", "color", "publicColor" }) + AnimalDto ignoredAndMappingAnimalToDto( Animal animal ); + + @Mapping(target = "publicColor", source = "publicColour") + @Ignored(targets = { "publicColor", "color" }) + AnimalDto ignoredAndMappingAnimalToDtoMap( Animal animal ); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.java new file mode 100644 index 0000000000..bc6fc2f6b7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/IgnoredPropertyTest.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.ignored; + +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; + +/** + * Test for ignoring properties during the mapping. + * + * @author Ivashin Aleksey + */ +@WithClasses({ Animal.class, AnimalDto.class, Zoo.class, ZooDto.class, ZooMapper.class}) +public class IgnoredPropertyTest { + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public 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.publicAge ).isNull(); + assertThat( animalDto.getColor() ).isNull(); + assertThat( animalDto.publicColor ).isNull(); + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { ErroneousMapper.class } ) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Target property \"name\" must not be mapped more than once." ), + @Diagnostic(type = ErroneousMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 24, + message = "Target property \"publicColor\" must not be mapped more than once." ) + } + ) + public void shouldFailToGenerateMappings() { + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredInnerPropertyGivenViaTargetAttribute() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + Zoo zoo = new Zoo(animal, "Test name", "test address"); + + ZooDto zooDto = ZooMapper.INSTANCE.zooToDto( zoo ); + + assertThat( zooDto ).isNotNull(); + assertThat( zooDto.getName() ).isEqualTo( "Test name" ); + assertThat( zooDto.getAddress() ).isEqualTo( "test address" ); + assertThat( zooDto.getAnimal() ).isNotNull(); + assertThat( zooDto.getAnimal().getName() ).isEqualTo( "Bruno" ); + assertThat( zooDto.getAnimal().getAge() ).isNull(); + assertThat( zooDto.getAnimal().publicAge ).isNull(); + assertThat( zooDto.getAnimal().getColor() ).isNull(); + assertThat( zooDto.getAnimal().publicColor ).isNull(); + assertThat( zooDto.getAnimal().getSize() ).isNull(); + } + + @ProcessorTest + @IssueKey("1958") + @WithClasses( { AnimalMapper.class } ) + public void shouldNotPropagateIgnoredInnerPropertyGivenViaTargetAttribute2() { + Animal animal = new Animal( "Bruno", 100, 23, "black" ); + Zoo zoo = new Zoo(animal, "Test name", "test address"); + + ZooDto zooDto = ZooMapper.INSTANCE.zooToDto2( zoo ); + + assertThat( zooDto ).isNotNull(); + assertThat( zooDto.getName() ).isEqualTo( "Test name" ); + assertThat( zooDto.getAddress() ).isNull(); + assertThat( zooDto.getAnimal() ).isNotNull(); + assertThat( zooDto.getAnimal().getName() ).isEqualTo( "Bruno" ); + assertThat( zooDto.getAnimal().getAge() ).isNull(); + assertThat( zooDto.getAnimal().publicAge ).isNull(); + assertThat( zooDto.getAnimal().getColor() ).isNull(); + assertThat( zooDto.getAnimal().publicColor ).isNull(); + assertThat( zooDto.getAnimal().getSize() ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.java new file mode 100644 index 0000000000..377e03b877 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/Zoo.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.ignored; + +public class Zoo { + + private Animal animal; + + private String name; + + private String address; + + public Zoo() { + } + + public Zoo(Animal animal, String name, String address ) { + this.animal = animal; + this.name = name; + this.address = address; + } + + public Animal getAnimal() { + return animal; + } + + public void setAnimal(Animal animal) { + this.animal = animal; + } + + 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/ignored/ZooDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.java new file mode 100644 index 0000000000..7c062cab4c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooDto.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.ignored; + +public class ZooDto { + + private AnimalDto animal; + + private String name; + + private String address; + + public ZooDto() { + } + + public ZooDto(AnimalDto animal, String name, String address) { + this.animal = animal; + this.name = name; + this.address = address; + } + + public AnimalDto getAnimal() { + return animal; + } + + public void setAnimal(AnimalDto animal) { + this.animal = animal; + } + + 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/ignored/ZooMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.java new file mode 100644 index 0000000000..f05e045b5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignored/ZooMapper.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.ignored; + +import org.mapstruct.Ignored; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ZooMapper { + + ZooMapper INSTANCE = Mappers.getMapper( ZooMapper.class ); + + @Ignored( prefix = "animal", targets = { "publicAge", "size", "publicColor", "age", "color" } ) + ZooDto zooToDto( Zoo zoo ); + + @Ignored( targets = { "address" } ) + @Ignored( prefix = "animal", targets = { "publicAge", "size", "publicColor", "age", "color" } ) + ZooDto zooToDto2( Zoo zoo ); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/IgnoredMappedPropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/IgnoredMappedPropertyTest.java new file mode 100644 index 0000000000..a8972e8a81 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/IgnoredMappedPropertyTest.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.test.ignoreunmapped; + +import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapper; +import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithWarnSourcePolicy; +import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithWarnPolicyInMapperConfig; +import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithWarnSourcePolicyInMapper; +import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithoutBeanMapping; +import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithIgnorePolicyInMapperConfig; +import org.mapstruct.ap.test.ignoreunmapped.mapper.UserMapperWithIgnoreSourcePolicy; +import org.mapstruct.ap.test.ignoreunmapped.mapper.erroneous.UserMapperWithErrorPolicyInMapperConfig; +import org.mapstruct.ap.test.ignoreunmapped.mapper.erroneous.UserMapperWithErrorSourcePolicy; +import org.mapstruct.ap.test.ignoreunmapped.mapper.erroneous.UserMapperWithErrorSourcePolicyInMapper; +import org.mapstruct.ap.test.ignoreunmapped.mapper.erroneous.UserMapperWithMultiMapping; +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 javax.tools.Diagnostic.Kind; + +/** + * Verifies that mapped properties listed in ignoreUnmappedSourceProperties trigger a warning. + * + * @author Ritesh Chopade(codeswithritesh) + */ +@IssueKey("3837") +@WithClasses({UserEntity.class, UserDto.class}) +public class IgnoredMappedPropertyTest { + + @ProcessorTest + @WithClasses({ + UserMapper.class, + UserMapperWithIgnoreSourcePolicy.class, + UserMapperWithoutBeanMapping.class, + UserMapperWithIgnorePolicyInMapperConfig.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED + ) + public void shouldNotWarnAboutRedundantIgnore() { + } + + @ProcessorTest + @WithClasses({ + UserMapperWithErrorSourcePolicy.class, + UserMapperWithErrorSourcePolicyInMapper.class, + UserMapperWithErrorPolicyInMapperConfig.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = UserMapperWithErrorSourcePolicy.class, + kind = Kind.ERROR, + line = 20, + message = "Source property \"email\" is mapped despite being " + + "listed in ignoreUnmappedSourceProperties." + ), + @Diagnostic( + type = UserMapperWithErrorSourcePolicyInMapper.class, + kind = Kind.ERROR, + line = 19, + message = "Source property \"email\" is mapped despite being " + + "listed in ignoreUnmappedSourceProperties." + ), + @Diagnostic( + type = UserMapperWithErrorPolicyInMapperConfig.class, + kind = Kind.ERROR, + line = 23, + message = "Source property \"email\" is mapped despite being " + + "listed in ignoreUnmappedSourceProperties." + ) + } + ) + public void shouldWarnAboutRedundantIgnoreWithErrorPolicy() { + } + + @ProcessorTest + @WithClasses({ + UserMapperWithWarnSourcePolicy.class, + UserMapperWithWarnSourcePolicyInMapper.class, + UserMapperWithWarnPolicyInMapperConfig.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + type = UserMapperWithWarnSourcePolicy.class, + kind = Kind.WARNING, + line = 20, + message = "Source property \"email\" is mapped despite being " + + "listed in ignoreUnmappedSourceProperties." + ), + @Diagnostic( + type = UserMapperWithWarnSourcePolicyInMapper.class, + kind = Kind.WARNING, + line = 19, + message = "Source property \"email\" is mapped despite being " + + "listed in ignoreUnmappedSourceProperties." + ), + @Diagnostic( + type = UserMapperWithWarnPolicyInMapperConfig.class, + kind = Kind.WARNING, + line = 23, + message = "Source property \"email\" is mapped despite being " + + "listed in ignoreUnmappedSourceProperties." + ) + } + ) + public void shouldWarnAboutRedundantIgnoreWithWarnPolicy() { + } + + @ProcessorTest + @WithClasses({UserMapperWithMultiMapping.class}) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + type = UserMapperWithMultiMapping.class, + kind = Kind.ERROR, + line = 21, + message = "Source properties \"email, username\" are mapped despite " + + "being listed in ignoreUnmappedSourceProperties." + ) + } + ) + public void shouldWarnAboutRedundantIgnoreWithMultiMapping() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserDto.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserDto.java new file mode 100644 index 0000000000..f153bbe8a7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserDto.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.ignoreunmapped; + +/** + * @author Ritesh Chopade(codeswithritesh) + */ +public class UserDto { + + private String username; + private String email; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserEntity.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserEntity.java new file mode 100644 index 0000000000..692cc4aa89 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/UserEntity.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.ignoreunmapped; + +/** + * @author Ritesh Chopade(codeswithritesh) + */ +public class UserEntity { + + private String username; + private String email; + private String password; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapper.java new file mode 100644 index 0000000000..bcb23677e4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapper.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.ignoreunmapped.mapper; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@Mapper +public interface UserMapper { + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnorePolicyInMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnorePolicyInMapperConfig.java new file mode 100644 index 0000000000..f73e0bc28c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnorePolicyInMapperConfig.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.ignoreunmapped.mapper; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@MapperConfig(unmappedSourcePolicy = ReportingPolicy.IGNORE) +interface IgnorePolicyConfig { } + +@Mapper(config = IgnorePolicyConfig.class) +public interface UserMapperWithIgnorePolicyInMapperConfig { + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnoreSourcePolicy.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnoreSourcePolicy.java new file mode 100644 index 0000000000..d844f1229a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithIgnoreSourcePolicy.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.ignoreunmapped.mapper; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@Mapper +public interface UserMapperWithIgnoreSourcePolicy { + + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}, unmappedSourcePolicy = ReportingPolicy.IGNORE) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnPolicyInMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnPolicyInMapperConfig.java new file mode 100644 index 0000000000..37184ea0d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnPolicyInMapperConfig.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.ignoreunmapped.mapper; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@MapperConfig(unmappedSourcePolicy = ReportingPolicy.WARN) +interface WarnPolicyConfig { } + +@Mapper(config = WarnPolicyConfig.class) +public interface UserMapperWithWarnPolicyInMapperConfig { + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicy.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicy.java new file mode 100644 index 0000000000..5a67934114 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicy.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.ignoreunmapped.mapper; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@Mapper +public interface UserMapperWithWarnSourcePolicy { + + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}, unmappedSourcePolicy = ReportingPolicy.WARN) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicyInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicyInMapper.java new file mode 100644 index 0000000000..2f34fc336f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithWarnSourcePolicyInMapper.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.ignoreunmapped.mapper; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@Mapper(unmappedSourcePolicy = ReportingPolicy.WARN) +public interface UserMapperWithWarnSourcePolicyInMapper { + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithoutBeanMapping.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithoutBeanMapping.java new file mode 100644 index 0000000000..10c9065bdf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/UserMapperWithoutBeanMapping.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.ignoreunmapped.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@Mapper +public interface UserMapperWithoutBeanMapping { + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorPolicyInMapperConfig.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorPolicyInMapperConfig.java new file mode 100644 index 0000000000..0d25facd69 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorPolicyInMapperConfig.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.ignoreunmapped.mapper.erroneous; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MapperConfig; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@MapperConfig(unmappedSourcePolicy = ReportingPolicy.ERROR) +interface ErrorPolicyConfig { } + +@Mapper(config = ErrorPolicyConfig.class) +public interface UserMapperWithErrorPolicyInMapperConfig { + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicy.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicy.java new file mode 100644 index 0000000000..db658b3636 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicy.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.ignoreunmapped.mapper.erroneous; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@Mapper +public interface UserMapperWithErrorSourcePolicy { + + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}, unmappedSourcePolicy = ReportingPolicy.ERROR) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicyInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicyInMapper.java new file mode 100644 index 0000000000..27a867ca61 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithErrorSourcePolicyInMapper.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.ignoreunmapped.mapper.erroneous; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) +public interface UserMapperWithErrorSourcePolicyInMapper { + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email"}) + @Mapping(source = "email", target = "email") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithMultiMapping.java b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithMultiMapping.java new file mode 100644 index 0000000000..388c00de2c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/ignoreunmapped/mapper/erroneous/UserMapperWithMultiMapping.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.ignoreunmapped.mapper.erroneous; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.ignoreunmapped.UserDto; +import org.mapstruct.ap.test.ignoreunmapped.UserEntity; + +@Mapper +public interface UserMapperWithMultiMapping { + @BeanMapping(ignoreUnmappedSourceProperties = {"password", "email", "username"}, + unmappedSourcePolicy = ReportingPolicy.ERROR) + @Mapping(source = "email", target = "email") + @Mapping(source = "username", target = "username") + UserDto map(UserEntity user); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/ConflictingTypesNamesTest.java b/processor/src/test/java/org/mapstruct/ap/test/imports/ConflictingTypesNamesTest.java index 0fda48c63d..8d61eb8f8a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/ConflictingTypesNamesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/ConflictingTypesNamesTest.java @@ -5,11 +5,7 @@ */ package org.mapstruct.ap.test.imports; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.imports.from.Foo; import org.mapstruct.ap.test.imports.from.FooWrapper; import org.mapstruct.ap.test.imports.referenced.GenericMapper; @@ -17,10 +13,13 @@ import org.mapstruct.ap.test.imports.referenced.Source; import org.mapstruct.ap.test.imports.referenced.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.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.WithJavaxInject; import org.mapstruct.ap.testutil.runner.GeneratedSource; +import static org.assertj.core.api.Assertions.assertThat; + /** * Test for generating a mapper which references types whose names clash with names of used annotations and exceptions. * @@ -43,17 +42,13 @@ org.mapstruct.ap.test.imports.to.FooWrapper.class, SecondSourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) +@WithJavaxInject public class ConflictingTypesNamesTest { - private final GeneratedSource generatedSource = new GeneratedSource(); - - @Rule - public GeneratedSource getGeneratedSource() { - return generatedSource; - } + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest public void mapperImportingTypesWithConflictingNamesCanBeGenerated() { Named source = new Named(); source.setFoo( 123 ); @@ -71,7 +66,7 @@ public void mapperImportingTypesWithConflictingNamesCanBeGenerated() { assertThat( foo2.getName() ).isEqualTo( "bar" ); } - @Test + @ProcessorTest @IssueKey("178") public void mapperHasNoUnnecessaryImports() { Source source = new Source(); @@ -91,7 +86,7 @@ public void mapperHasNoUnnecessaryImports() { generatedSource.forMapper( SecondSourceTargetMapper.class ).containsNoImportFor( NotImportedDatatype.class ); } - @Test + @ProcessorTest @IssueKey("156") public void importsForTargetTypes() { FooWrapper source = new FooWrapper(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/InnerClassesImportsTest.java b/processor/src/test/java/org/mapstruct/ap/test/imports/InnerClassesImportsTest.java index 95dbbd3ee8..abad5e4177 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/InnerClassesImportsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/InnerClassesImportsTest.java @@ -5,11 +5,7 @@ */ package org.mapstruct.ap.test.imports; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.imports.innerclasses.BeanFacade; import org.mapstruct.ap.test.imports.innerclasses.BeanWithInnerEnum; import org.mapstruct.ap.test.imports.innerclasses.BeanWithInnerEnum.InnerEnum; @@ -18,13 +14,14 @@ import org.mapstruct.ap.test.imports.innerclasses.SourceWithInnerClass; import org.mapstruct.ap.test.imports.innerclasses.SourceWithInnerClass.SourceInnerClass; import org.mapstruct.ap.test.imports.innerclasses.TargetWithInnerClass; -import org.mapstruct.ap.test.imports.innerclasses.TargetWithInnerClass.TargetInnerClass; import org.mapstruct.ap.test.imports.innerclasses.TargetWithInnerClass.TargetInnerClass.TargetInnerInnerClass; 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; + /** * Test for generating a mapper which references nested types (static inner classes). * @@ -34,17 +31,12 @@ SourceWithInnerClass.class, TargetWithInnerClass.class, InnerClassMapper.class, // BeanFacade.class, BeanWithInnerEnum.class, BeanWithInnerEnumMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class InnerClassesImportsTest { - private final GeneratedSource generatedSource = new GeneratedSource(); - - @Rule - public GeneratedSource getGeneratedSource() { - return generatedSource; - } + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest @IssueKey( "412" ) public void mapperRequiresInnerClassImports() { SourceWithInnerClass source = new SourceWithInnerClass(); @@ -54,11 +46,10 @@ public void mapperRequiresInnerClassImports() { assertThat( target ).isNotNull(); assertThat( target.getInnerClassMember().getValue() ).isEqualTo( 412 ); - generatedSource.forMapper( InnerClassMapper.class ).containsImportFor( SourceInnerClass.class ); - generatedSource.forMapper( InnerClassMapper.class ).containsImportFor( TargetInnerClass.class ); + generatedSource.addComparisonToFixtureFor( InnerClassMapper.class ); } - @Test + @ProcessorTest @IssueKey( "412" ) public void mapperRequiresInnerInnerClassImports() { SourceInnerClass source = new SourceInnerClass(); @@ -68,11 +59,10 @@ public void mapperRequiresInnerInnerClassImports() { assertThat( target ).isNotNull(); assertThat( target.getValue() ).isEqualTo( 412 ); - generatedSource.forMapper( InnerClassMapper.class ).containsImportFor( SourceInnerClass.class ); - generatedSource.forMapper( InnerClassMapper.class ).containsImportFor( TargetInnerInnerClass.class ); + generatedSource.addComparisonToFixtureFor( InnerClassMapper.class ); } - @Test + @ProcessorTest @IssueKey( "209" ) public void mapperRequiresInnerEnumImports() { BeanWithInnerEnum source = new BeanWithInnerEnum(); @@ -89,6 +79,6 @@ public void mapperRequiresInnerEnumImports() { assertThat( sourceAgain ).isNotNull(); assertThat( sourceAgain.getInnerEnum() ).isEqualTo( InnerEnum.A ); - generatedSource.forMapper( BeanWithInnerEnumMapper.class ).containsImportFor( InnerEnum.class ); + generatedSource.addComparisonToFixtureFor( BeanWithInnerEnumMapper.class ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java index 8e5e7b0de2..845544ba9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/SourceTargetMapper.java @@ -8,6 +8,7 @@ import java.util.Date; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.ap.test.imports.referenced.Source; import org.mapstruct.ap.test.imports.referenced.Target; import org.mapstruct.ap.test.imports.to.Foo; @@ -16,7 +17,7 @@ /** * @author Gunnar Morling */ -@Mapper(componentModel = "jsr330") +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @@ -24,7 +25,7 @@ public interface SourceTargetMapper { ParseException sourceToTarget(Named source); //custom types - Map listToMap(List list); + Value listToMap(List list); java.util.List namedsToExceptions(java.util.List source); @@ -35,4 +36,12 @@ public interface SourceTargetMapper { java.util.Map stringsToDates(java.util.Map stringDates); Target sourceToTarget(Source target); + + class Value { + private T value; + + public Value(List list) { + + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/decorator/DecoratorInAnotherPackageTest.java b/processor/src/test/java/org/mapstruct/ap/test/imports/decorator/DecoratorInAnotherPackageTest.java index 13f373278f..90249b319e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/decorator/DecoratorInAnotherPackageTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/decorator/DecoratorInAnotherPackageTest.java @@ -5,14 +5,12 @@ */ package org.mapstruct.ap.test.imports.decorator; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.imports.decorator.other.ActorMapperDecorator; 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; /** * Test for having a decorator in another package than the mapper interface. @@ -26,10 +24,9 @@ ActorMapper.class, ActorMapperDecorator.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class DecoratorInAnotherPackageTest { - @Test + @ProcessorTest public void shouldApplyDecoratorFromAnotherPackage() { Actor actor = new Actor(); actor.setAwards( 3 ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedImportsTest.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedImportsTest.java new file mode 100644 index 0000000000..37c522e4cd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedImportsTest.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.imports.nested; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.imports.nested.other.SourceInOtherPackage; +import org.mapstruct.ap.test.imports.nested.other.TargetInOtherPackage; +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 + */ +@WithClasses({ + SourceInOtherPackage.class, + TargetInOtherPackage.class, + Source.class, + Target.class +}) +@IssueKey("1386,148") +class NestedImportsTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses( { + SourceInOtherPackageMapper.class + } ) + void shouldGenerateNestedInnerClassesForSourceInOtherPackage() { + generatedSource.addComparisonToFixtureFor( SourceInOtherPackageMapper.class ); + } + + @ProcessorTest + @WithClasses( { + NestedSourceInOtherPackageMapper.class + } ) + void shouldGenerateCorrectImportsForTopLevelClassesFromOnlyNestedInnerClasses() { + generatedSource.addComparisonToFixtureFor( NestedSourceInOtherPackageMapper.class ); + } + + @ProcessorTest + @WithClasses( { + TargetInOtherPackageMapper.class + } ) + void shouldGenerateNestedInnerClassesForTargetInOtherPackage() { + generatedSource.addComparisonToFixtureFor( TargetInOtherPackageMapper.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapper.java new file mode 100644 index 0000000000..7f98737d1d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/NestedSourceInOtherPackageMapper.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.imports.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.imports.nested.other.SourceInOtherPackage; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface NestedSourceInOtherPackageMapper { + + Target.Nested map(SourceInOtherPackage.Nested source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Source.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Source.java new file mode 100644 index 0000000000..2d2e9aa215 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/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.imports.nested; + +/** + * @author Filip Hrisafov + */ +public class Source { + + private Nested value; + + public Nested getValue() { + return value; + } + + public void setValue(Nested value) { + this.value = value; + } + + public static class Nested { + + private Inner inner; + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapper.java new file mode 100644 index 0000000000..7150823c1a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/SourceInOtherPackageMapper.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.imports.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.imports.nested.other.SourceInOtherPackage; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SourceInOtherPackageMapper { + + Target map(SourceInOtherPackage source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Target.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/Target.java new file mode 100644 index 0000000000..78a50fcb15 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/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.imports.nested; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private Nested value; + + public Nested getValue() { + return value; + } + + public void setValue(Nested value) { + this.value = value; + } + + public static class Nested { + + private Inner inner; + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapper.java new file mode 100644 index 0000000000..a40d52eb1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/TargetInOtherPackageMapper.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.imports.nested; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.imports.nested.other.TargetInOtherPackage; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface TargetInOtherPackageMapper { + + TargetInOtherPackage map(Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/SourceInOtherPackage.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/SourceInOtherPackage.java new file mode 100644 index 0000000000..f6646c7160 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/SourceInOtherPackage.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.imports.nested.other; + +/** + * @author Filip Hrisafov + */ +public class SourceInOtherPackage { + + private Nested value; + + public Nested getValue() { + return value; + } + + public void setValue(Nested value) { + this.value = value; + } + + public static class Nested { + + private Inner inner; + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/TargetInOtherPackage.java b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/TargetInOtherPackage.java new file mode 100644 index 0000000000..da6b596c06 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/nested/other/TargetInOtherPackage.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.imports.nested.other; + +/** + * @author Filip Hrisafov + */ +public class TargetInOtherPackage { + + private Nested value; + + public Nested getValue() { + return value; + } + + public void setValue(Nested value) { + this.value = value; + } + + public static class Nested { + + private Inner inner; + + public static class Inner { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + public Inner getInner() { + return inner; + } + + public void setInner(Inner inner) { + this.inner = inner; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/imports/sourcewithextendsbound/SourceTypeContainsCollectionWithExtendsBoundTest.java b/processor/src/test/java/org/mapstruct/ap/test/imports/sourcewithextendsbound/SourceTypeContainsCollectionWithExtendsBoundTest.java index 069e23a58f..37f4b7a0a7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/imports/sourcewithextendsbound/SourceTypeContainsCollectionWithExtendsBoundTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/imports/sourcewithextendsbound/SourceTypeContainsCollectionWithExtendsBoundTest.java @@ -5,23 +5,21 @@ */ package org.mapstruct.ap.test.imports.sourcewithextendsbound; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Collections; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.imports.sourcewithextendsbound.astronautmapper.AstronautMapper; import org.mapstruct.ap.test.imports.sourcewithextendsbound.dto.AstronautDto; import org.mapstruct.ap.test.imports.sourcewithextendsbound.dto.SpaceshipDto; import org.mapstruct.ap.test.imports.sourcewithextendsbound.entity.Astronaut; import org.mapstruct.ap.test.imports.sourcewithextendsbound.entity.Spaceship; 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; + /** * Test for generating a mapper which references nested types (static inner classes). * @@ -31,17 +29,12 @@ Astronaut.class, Spaceship.class, AstronautDto.class, SpaceshipDto.class, SpaceshipMapper.class, AstronautMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class SourceTypeContainsCollectionWithExtendsBoundTest { - private final GeneratedSource generatedSource = new GeneratedSource(); - - @Rule - public GeneratedSource getGeneratedSource() { - return generatedSource; - } + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest @IssueKey("768") public void generatesImportsForCollectionWithExtendsBoundInSourceType() { Astronaut astronaut = new Astronaut(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java index b13629d06c..aaf299635e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.inheritance; -import static org.assertj.core.api.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; /** * Test for propagation of attributes inherited from super types. @@ -19,10 +17,9 @@ * @author Gunnar Morling */ @WithClasses({ SourceBase.class, SourceExt.class, TargetBase.class, TargetExt.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class InheritanceTest { - @Test + @ProcessorTest @IssueKey("17") public void shouldMapAttributeFromSuperType() { SourceExt source = createSource(); @@ -32,7 +29,7 @@ public void shouldMapAttributeFromSuperType() { assertResult( target ); } - @Test + @ProcessorTest @IssueKey("19") public void shouldMapAttributeFromSuperTypeUsingTargetParameter() { SourceExt source = createSource(); @@ -43,7 +40,7 @@ public void shouldMapAttributeFromSuperTypeUsingTargetParameter() { assertResult( target ); } - @Test + @ProcessorTest @IssueKey("19") public void shouldMapAttributeFromSuperTypeUsingReturnedTargetParameter() { SourceExt source = createSource(); @@ -56,6 +53,20 @@ public void shouldMapAttributeFromSuperTypeUsingReturnedTargetParameter() { assertResult( target ); } + @ProcessorTest + @IssueKey("1752") + public void shouldMapAttributeFromSuperTypeUsingReturnedTargetParameterAndNullSource() { + + TargetExt target = new TargetExt(); + target.setFoo( 42L ); + target.publicFoo = 52L; + target.setBar( 23 ); + TargetBase result = SourceTargetMapper.INSTANCE.sourceToTargetWithTargetParameterAndReturn( null, target ); + + assertThat( target ).isSameAs( result ); + assertResult( target ); + } + private void assertResult(TargetExt target) { assertThat( target ).isNotNull(); assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) ); @@ -71,7 +82,7 @@ private SourceExt createSource() { return source; } - @Test + @ProcessorTest @IssueKey("17") public void shouldReverseMapAttributeFromSuperType() { TargetExt target = new TargetExt(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritance/attribute/AttributeInheritanceTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritance/attribute/AttributeInheritanceTest.java index 839d0987f7..7b9f2d6196 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritance/attribute/AttributeInheritanceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritance/attribute/AttributeInheritanceTest.java @@ -5,27 +5,24 @@ */ package org.mapstruct.ap.test.inheritance.attribute; -import static org.assertj.core.api.Assertions.assertThat; - 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; + +import static org.assertj.core.api.Assertions.assertThat; /** * Test for setting an attribute where the target attribute of a super-type. * * @author Gunnar Morling */ -@RunWith(AnnotationProcessorTestRunner.class) public class AttributeInheritanceTest { - @Test + @ProcessorTest @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) public void shouldMapAttributeFromSuperType() { Source source = new Source(); @@ -36,7 +33,7 @@ public void shouldMapAttributeFromSuperType() { assertThat( target.getFoo().toString() ).isEqualTo( "Bob" ); } - @Test + @ProcessorTest @WithClasses({ Source.class, Target.class, ErroneousTargetSourceMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -44,7 +41,8 @@ public void shouldMapAttributeFromSuperType() { type = ErroneousTargetSourceMapper.class, kind = Kind.ERROR, line = 16, - messageRegExp = "Can't map property \"java.lang.CharSequence foo\" to \"java.lang.String foo\"" + message = "Can't map property \"CharSequence foo\" to \"String foo\". " + + "Consider to declare/implement a mapping method: \"String map(CharSequence value)\"." )) public void shouldReportErrorDueToUnmappableAttribute() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritance/complex/ComplexInheritanceTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritance/complex/ComplexInheritanceTest.java index 6168ecdf24..e2dc947d35 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritance/complex/ComplexInheritanceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritance/complex/ComplexInheritanceTest.java @@ -5,20 +5,17 @@ */ package org.mapstruct.ap.test.inheritance.complex; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; - 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 propagation of attributes inherited from super types. @@ -30,10 +27,9 @@ SourceExt.class, SourceExt2.class, TargetComposite.class, AdditionalFooSource.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ComplexInheritanceTest { - @Test + @ProcessorTest @IssueKey("34") @WithClasses({ StandaloneSourceCompositeTargetCompositeMapper.class }) public void shouldMapAttributesWithSuperTypeInStandaloneMapper() { @@ -47,7 +43,7 @@ public void shouldMapAttributesWithSuperTypeInStandaloneMapper() { assertThat( target.getProp5() ).containsOnly( 42, 999 ); } - @Test + @ProcessorTest @IssueKey("34") @WithClasses({ SourceCompositeTargetCompositeMapper.class, SourceBaseMappingHelper.class }) public void shouldMapAttributesWithSuperTypeUsingOtherMapper() { @@ -61,7 +57,7 @@ public void shouldMapAttributesWithSuperTypeUsingOtherMapper() { assertThat( target.getProp5() ).containsOnly( 43, 1000 ); } - @Test + @ProcessorTest @IssueKey("34") @WithClasses({ ErroneousSourceCompositeTargetCompositeMapper.class, AdditionalMappingHelper.class }) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, @@ -69,11 +65,11 @@ public void shouldMapAttributesWithSuperTypeUsingOtherMapper() { kind = Kind.ERROR, type = ErroneousSourceCompositeTargetCompositeMapper.class, line = 19, - messageRegExp = - "Ambiguous mapping methods found for mapping property " - + "\"org.mapstruct.ap.test.inheritance.complex.SourceExt prop1\" to .*Reference: " - + ".*Reference .*AdditionalMappingHelper\\.asReference\\(.*SourceBase source\\), " - + ".*Reference .*AdditionalMappingHelper\\.asReference\\(.*AdditionalFooSource source\\)")) + message = "Ambiguous mapping methods found for mapping property \"SourceExt prop1\" to Reference: " + + "Reference AdditionalMappingHelper.asReference(SourceBase source), " + + "Reference AdditionalMappingHelper.asReference(AdditionalFooSource source). " + + "See https://mapstruct.org/faq/#ambiguous for more info.") + ) public void ambiguousMappingMethodsReportError() { } @@ -108,14 +104,14 @@ private SourceBase createSourceBase(int foo) { private SourceExt createSourceExt(int foo) { SourceExt s = new SourceExt(); s.setFoo( foo ); - s.setBar( Long.valueOf( 47 ) ); + s.setBar( 47L ); return s; } private SourceExt2 createSourceExt2(int foo) { SourceExt2 s = new SourceExt2(); s.setFoo( foo ); - s.setBaz( Long.valueOf( 47 ) ); + s.setBaz( 47L ); return s; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritance/complex/SourceBaseMappingHelper.java b/processor/src/test/java/org/mapstruct/ap/test/inheritance/complex/SourceBaseMappingHelper.java index e73c708915..017b98496b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritance/complex/SourceBaseMappingHelper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritance/complex/SourceBaseMappingHelper.java @@ -5,9 +5,9 @@ */ package org.mapstruct.ap.test.inheritance.complex; -import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; public class SourceBaseMappingHelper { public Reference asReference(SourceBase source) { @@ -33,13 +33,8 @@ public List integerCollectionToNumberList(Collection collection if ( collection == null ) { return null; } - - List list = new ArrayList( collection.size() ); - - for ( Integer original : collection ) { - list.add( Integer.valueOf( original + 1 ) ); - } - - return list; + return collection.stream() + .map( original -> original + 1 ) + .collect( Collectors.toList() ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java index 874acfc81d..36c7c5ea08 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritedmappingmethod/InheritedMappingMethodTest.java @@ -5,33 +5,30 @@ */ package org.mapstruct.ap.test.inheritedmappingmethod; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.inheritedmappingmethod._target.CarDto; import org.mapstruct.ap.test.inheritedmappingmethod._target.FastCarDto; import org.mapstruct.ap.test.inheritedmappingmethod.source.Car; import org.mapstruct.ap.test.inheritedmappingmethod.source.FastCar; 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( "274" ) @WithClasses({ Car.class, CarDto.class, UnboundMappable.class, CarMapper.class, // FastCar.class, FastCarDto.class, BoundMappable.class, FastCarMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class InheritedMappingMethodTest { - @Test + @ProcessorTest public void shouldProvideUnboundedMapperInstance() { UnboundMappable instance = CarMapper.INSTANCE; assertThat( instance ).isNotNull(); } - @Test + @ProcessorTest public void shouldMapUsingUnboundedInheretedMappingMethod() { // given CarDto bikeDto = new CarDto(); @@ -45,13 +42,13 @@ public void shouldMapUsingUnboundedInheretedMappingMethod() { assertThat( bike.getHorsepower() ).isEqualTo( 130 ); } - @Test + @ProcessorTest public void shouldProvideBoundedMapperInstance() { BoundMappable instance = FastCarMapper.INSTANCE; assertThat( instance ).isNotNull(); } - @Test + @ProcessorTest public void shouldMapUsingBoundedInheretedMappingMethod() { // given FastCarDto bikeDto = new FastCarDto(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/InheritFromConfigTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/InheritFromConfigTest.java index 29c01c0a6d..4d8e0c28ab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/InheritFromConfigTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/InheritFromConfigTest.java @@ -5,23 +5,20 @@ */ package org.mapstruct.ap.test.inheritfromconfig; -import static org.assertj.core.api.Assertions.assertThat; - 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; /** * @author Andreas Gudian */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ BaseVehicleDto.class, BaseVehicleEntity.class, @@ -35,7 +32,7 @@ @IssueKey("168") public class InheritFromConfigTest { - @Test + @ProcessorTest public void autoInheritedMappingIsApplied() { CarDto carDto = newTestDto(); @@ -44,7 +41,7 @@ public void autoInheritedMappingIsApplied() { assertEntity( carEntity ); } - @Test + @ProcessorTest public void autoInheritedMappingIsAppliedForMappingTarget() { CarDto carDto = newTestDto(); CarEntity carEntity = new CarEntity(); @@ -54,7 +51,7 @@ public void autoInheritedMappingIsAppliedForMappingTarget() { assertEntity( carEntity ); } - @Test + @ProcessorTest public void autoInheritedMappingIsAppliedForMappingTargetWithTwoStepInheritance() { CarDto carDto = newTestDto(); CarEntity carEntity = new CarEntity(); @@ -77,7 +74,7 @@ private CarDto newTestDto() { return carDto; } - @Test + @ProcessorTest public void autoInheritedMappingIsOverriddenAtMethodLevel() { CarDto carDto = newTestDto(); @@ -88,7 +85,7 @@ public void autoInheritedMappingIsOverriddenAtMethodLevel() { assertThat( carEntity.getAuditTrail() ).isEqualTo( "fixed" ); } - @Test + @ProcessorTest public void autoInheritedMappingIsAppliedInReverse() { CarEntity carEntity = new CarEntity(); carEntity.setColor( "red" ); @@ -100,7 +97,7 @@ public void autoInheritedMappingIsAppliedInReverse() { assertThat( carDto.getId() ).isEqualTo( 42L ); } - @Test + @ProcessorTest public void explicitInheritedMappingIsAppliedInReverse() { CarEntity carEntity = new CarEntity(); carEntity.setColor( "red" ); @@ -112,7 +109,7 @@ public void explicitInheritedMappingIsAppliedInReverse() { assertThat( carDto.getId() ).isEqualTo( 42L ); } - @Test + @ProcessorTest @IssueKey( "1065" ) @WithClasses({ CarMapperReverseWithExplicitInheritance.class } ) public void explicitInheritedMappingIsAppliedInReverseDirectlyFromConfig() { @@ -127,7 +124,7 @@ public void explicitInheritedMappingIsAppliedInReverseDirectlyFromConfig() { assertThat( carDto.getId() ).isEqualTo( 42L ); } - @Test + @ProcessorTest @IssueKey( "1255" ) @WithClasses({ CarMapperReverseWithAutoInheritance.class, AutoInheritedReverseConfig.class } ) public void autoInheritedMappingIsAppliedInReverseDirectlyFromConfig() { @@ -142,7 +139,7 @@ public void autoInheritedMappingIsAppliedInReverseDirectlyFromConfig() { assertThat( carDto.getId() ).isEqualTo( 42L ); } - @Test + @ProcessorTest @IssueKey( "1255" ) @WithClasses({ CarMapperAllWithAutoInheritance.class, AutoInheritedAllConfig.class } ) public void autoInheritedMappingIsAppliedInForwardAndReverseDirectlyFromConfig() { @@ -156,7 +153,7 @@ public void autoInheritedMappingIsAppliedInForwardAndReverseDirectlyFromConfig() assertThat( carDto.getId() ).isEqualTo( carDto2.getId() ); } - @Test + @ProcessorTest public void explicitInheritedMappingWithTwoLevelsIsOverriddenAtMethodLevel() { CarDto carDto = newTestDto(); @@ -167,7 +164,7 @@ public void explicitInheritedMappingWithTwoLevelsIsOverriddenAtMethodLevel() { assertThat( carEntity.getAuditTrail() ).isEqualTo( "fixed" ); } - @Test + @ProcessorTest public void explicitInheritedMappingIsApplied() { CarDto carDto = newTestDto(); @@ -176,7 +173,7 @@ public void explicitInheritedMappingIsApplied() { assertEntity( carEntity ); } - @Test + @ProcessorTest @WithClasses({ DriverDto.class, CarWithDriverEntity.class, @@ -195,7 +192,7 @@ public void autoInheritedFromMultipleSources() { assertThat( carWithDriverEntity.getDriverName() ).isEqualTo( "Malcroft" ); } - @Test + @ProcessorTest @WithClasses({ Erroneous1Mapper.class, Erroneous1Config.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -203,32 +200,31 @@ public void autoInheritedFromMultipleSources() { @Diagnostic(type = Erroneous1Mapper.class, kind = Kind.ERROR, line = 23, - messageRegExp = "More than one configuration prototype method is applicable. Use @InheritConfiguration" - + " to select one of them explicitly:" - + " .*BaseVehicleEntity baseDtoToEntity\\(.*BaseVehicleDto dto\\)," - + " .*BaseVehicleEntity anythingToEntity\\(java.lang.Object anyting\\)\\."), + message = "More than one configuration prototype method is applicable. Use @InheritConfiguration to " + + "select one of them explicitly: org.mapstruct.ap.test.inheritfromconfig.BaseVehicleEntity " + + "baseDtoToEntity(org.mapstruct.ap.test.inheritfromconfig.BaseVehicleDto dto), org.mapstruct.ap" + + ".test.inheritfromconfig.BaseVehicleEntity anythingToEntity(java.lang.Object anyting)."), @Diagnostic(type = Erroneous1Mapper.class, kind = Kind.WARNING, line = 23, - messageRegExp = "Unmapped target properties: \"primaryKey, auditTrail\"\\."), + message = "Unmapped target properties: \"primaryKey, auditTrail\"."), @Diagnostic(type = Erroneous1Mapper.class, kind = Kind.ERROR, line = 29, - messageRegExp = "More than one configuration prototype method is applicable. Use @InheritConfiguration" - + " to select one of them explicitly:" - + " .*BaseVehicleEntity baseDtoToEntity\\(.*BaseVehicleDto dto\\)," - + " .*BaseVehicleEntity anythingToEntity\\(java.lang.Object anyting\\)\\."), + message = "More than one configuration prototype method is applicable. Use @InheritConfiguration to " + + "select one of them explicitly: org.mapstruct.ap.test.inheritfromconfig.BaseVehicleEntity " + + "baseDtoToEntity(org.mapstruct.ap.test.inheritfromconfig.BaseVehicleDto dto), org.mapstruct.ap" + + ".test.inheritfromconfig.BaseVehicleEntity anythingToEntity(java.lang.Object anyting)."), @Diagnostic(type = Erroneous1Mapper.class, kind = Kind.WARNING, line = 29, - messageRegExp = "Unmapped target property: \"primaryKey\"\\.") + message = "Unmapped target property: \"primaryKey\".") } ) public void erroneous1MultiplePrototypeMethodsMatch() { - } - @Test + @ProcessorTest @WithClasses({ Erroneous2Mapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -236,18 +232,19 @@ public void erroneous1MultiplePrototypeMethodsMatch() { @Diagnostic(type = Erroneous2Mapper.class, kind = Kind.ERROR, line = 25, - messageRegExp = "Cycle detected while evaluating inherited configurations. Inheritance path:" - + " .*CarEntity toCarEntity1\\(.*CarDto carDto\\)" - + " -> .*CarEntity toCarEntity2\\(.*CarDto carDto\\)" - + " -> void toCarEntity3\\(.*CarDto carDto, @MappingTarget .*CarEntity entity\\)" - + " -> .*CarEntity toCarEntity1\\(.*CarDto carDto\\)") + message = "Cycle detected while evaluating inherited configurations. Inheritance path: org.mapstruct" + + ".ap.test.inheritfromconfig.CarEntity toCarEntity1(org.mapstruct.ap.test.inheritfromconfig.CarDto" + + " carDto) -> org.mapstruct.ap.test.inheritfromconfig.CarEntity toCarEntity2(org.mapstruct.ap.test" + + ".inheritfromconfig.CarDto carDto) -> void toCarEntity3(org.mapstruct.ap.test.inheritfromconfig" + + ".CarDto carDto, @MappingTarget org.mapstruct.ap.test.inheritfromconfig.CarEntity entity) -> org" + + ".mapstruct.ap.test.inheritfromconfig.CarEntity toCarEntity1(org.mapstruct.ap.test" + + ".inheritfromconfig.CarDto carDto)") } ) public void erroneous2InheritanceCycle() { - } - @Test + @ProcessorTest @IssueKey( "1255" ) @WithClasses({ ErroneousMapperAutoInheritance.class, AutoInheritedReverseConfig.class } ) @ExpectedCompilationOutcome( @@ -256,12 +253,12 @@ public void erroneous2InheritanceCycle() { @Diagnostic(type = ErroneousMapperAutoInheritance.class, kind = Kind.ERROR, line = 22, - messageRegExp = "Unmapped target properties: \"primaryKey, auditTrail\"\\.") + message = "Unmapped target properties: \"primaryKey, auditTrail\".") } ) public void erroneousWrongReverseConfigInherited() { } - @Test + @ProcessorTest @IssueKey( "1255" ) @WithClasses({ ErroneousMapperReverseWithAutoInheritance.class, AutoInheritedConfig.class } ) @ExpectedCompilationOutcome( @@ -270,28 +267,32 @@ public void erroneousWrongReverseConfigInherited() { } @Diagnostic(type = ErroneousMapperReverseWithAutoInheritance.class, kind = Kind.ERROR, line = 23, - messageRegExp = "Unmapped target property: \"id\"\\.") + message = "Unmapped target property: \"id\".") } ) public void erroneousWrongConfigInherited() { } - @Test - @IssueKey( "1255" ) - @WithClasses({ Erroneous3Mapper.class, Erroneous3Config.class } ) + @ProcessorTest + @IssueKey("1255") + @WithClasses({ Erroneous3Mapper.class, Erroneous3Config.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = Erroneous3Mapper.class, kind = Kind.ERROR, line = 22, - messageRegExp = "More than one configuration prototype method is applicable. " - + "Use @InheritInverseConfiguration.*"), + message = "More than one configuration prototype method is applicable. Use " + + "@InheritInverseConfiguration to select one of them explicitly: org.mapstruct.ap.test" + + ".inheritfromconfig.BaseVehicleEntity baseDtoToEntity(org.mapstruct.ap.test.inheritfromconfig" + + ".BaseVehicleDto dto), org.mapstruct.ap.test.inheritfromconfig.BaseVehicleEntity baseDtoToEntity2" + + "(org.mapstruct.ap.test.inheritfromconfig.BaseVehicleDto dto)."), @Diagnostic(type = Erroneous3Mapper.class, kind = Kind.ERROR, line = 22, - messageRegExp = "Unmapped target property: \"id\"\\.") + message = "Unmapped target property: \"id\".") } ) - public void erroneousDuplicateReverse() { } + public void erroneousDuplicateReverse() { + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/multiple/CarMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/multiple/CarMapperTest.java index 3e765d9b18..bc59060e13 100755 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/multiple/CarMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritfromconfig/multiple/CarMapperTest.java @@ -5,17 +5,14 @@ */ package org.mapstruct.ap.test.inheritfromconfig.multiple; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Date; -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; -@RunWith(AnnotationProcessorTestRunner.class) +import static org.assertj.core.api.Assertions.assertThat; + @WithClasses({ BaseDto.class, BaseEntity.class, @@ -30,7 +27,7 @@ @IssueKey("1367") public class CarMapperTest { - @Test + @ProcessorTest public void testMapEntityToDto() { CarDto dto = CarMapper.MAPPER.mapTo( newCar() ); assertThat( dto.getDbId() ).isEqualTo( 9L ); @@ -38,7 +35,7 @@ public void testMapEntityToDto() { assertThat( dto.getSeatCount() ).isEqualTo( 5 ); } - @Test + @ProcessorTest public void testMapDtoToEntity() { CarEntity car = CarMapper.MAPPER.mapFrom( newCarDto() ); assertThat( car.getId() ).isEqualTo( 9L ); @@ -48,7 +45,7 @@ public void testMapDtoToEntity() { assertThat( car.getCreationDate() ).isNull(); } - @Test + @ProcessorTest public void testForwardMappingShouldTakePrecedence() { Car2Dto dto = new Car2Dto(); dto.setMaker( "mazda" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..bf8b292ec7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CdiDefaultCompileOptionFieldMapperTest.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.injectionstrategy.cdi._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithCdi; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta-cdi. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerCdiDefaultCompileOptionFieldMapper.class, + GenderCdiDefaultCompileOptionFieldMapper.class +}) +@WithCdi +public class CdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveFieldInjection() { + generatedSource.forMapper( CustomerCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import javax.enterprise.context.ApplicationScoped;" ) + .contains( "import javax.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "public CustomerCdiDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "jakarta.inject" ) + .doesNotContain( "jakarta.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CustomerCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CustomerCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..ed0ed0a76e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/CustomerCdiDefaultCompileOptionFieldMapper.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.injectionstrategy.cdi._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.CDI, + uses = GenderCdiDefaultCompileOptionFieldMapper.class) +public interface CustomerCdiDefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/GenderCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/GenderCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..14d6a2e0d7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/_default/GenderCdiDefaultCompileOptionFieldMapper.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.injectionstrategy.cdi._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.CDI) +public interface GenderCdiDefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/CustomerCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/CustomerCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..4c893b7486 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/CustomerCdiDefaultCompileOptionFieldMapper.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.injectionstrategy.cdi.jakarta; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.CDI, + uses = GenderCdiDefaultCompileOptionFieldMapper.class) +public interface CustomerCdiDefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/GenderCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/GenderCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..8664b0b433 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/GenderCdiDefaultCompileOptionFieldMapper.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.injectionstrategy.cdi.jakarta; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.CDI) +public interface GenderCdiDefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..55480399a3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.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.injectionstrategy.cdi.jakarta; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithCdi; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaCdi; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerCdiDefaultCompileOptionFieldMapper.class, + GenderCdiDefaultCompileOptionFieldMapper.class +}) +@WithJakartaCdi +@WithCdi +public class JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveCdiInjection() { + generatedSource.forMapper( CustomerCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import javax.enterprise.context.ApplicationScoped;" ) + .contains( "import javax.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "public CustomerCdiDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "jakarta.inject" ) + .doesNotContain( "jakarta.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiCdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiCdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..953747e404 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/cdi/jakarta/JakartaCdiCdiDefaultCompileOptionFieldMapperTest.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.injectionstrategy.cdi.jakarta; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaCdi; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerCdiDefaultCompileOptionFieldMapper.class, + GenderCdiDefaultCompileOptionFieldMapper.class +}) +@WithJakartaCdi +public class JakartaCdiCdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaCdiInjection() { + generatedSource.forMapper( CustomerCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.enterprise.context.ApplicationScoped;" ) + .contains( "import jakarta.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "public CustomerCdiDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ) + .doesNotContain( "javax.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/CustomerJakartaDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/CustomerJakartaDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..2fb6ae0d07 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/CustomerJakartaDefaultCompileOptionFieldMapper.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.injectionstrategy.jakarta._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA, + uses = GenderJakartaDefaultCompileOptionFieldMapper.class) +public interface CustomerJakartaDefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/GenderJakartaDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/GenderJakartaDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..da3409ef41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/GenderJakartaDefaultCompileOptionFieldMapper.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.injectionstrategy.jakarta._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA) +public interface GenderJakartaDefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..c647605e66 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.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.injectionstrategy.jakarta._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaDefaultCompileOptionFieldMapper.class, + GenderJakartaDefaultCompileOptionFieldMapper.class +}) +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330DefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaDefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJakartaDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..2d9eddf236 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/_default/JakartaDefaultCompileOptionFieldMapperTest.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.injectionstrategy.jakarta._default; + +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.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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 field injection for component model jakarta. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaDefaultCompileOptionFieldMapper.class, + GenderJakartaDefaultCompileOptionFieldMapper.class +}) +@ComponentScan(basePackageClasses = CustomerJakartaDefaultCompileOptionFieldMapper.class) +@WithJakartaInject +@Configuration +public class JakartaDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Inject + @Named + private CustomerJakartaDefaultCompileOptionFieldMapper customerMapper; + 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 shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaDefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJakartaDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/CustomerJakartaCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/CustomerJakartaCompileOptionConstructorMapper.java new file mode 100644 index 0000000000..9775d05c87 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/CustomerJakartaCompileOptionConstructorMapper.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.injectionstrategy.jakarta.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JAKARTA, + uses = GenderJakartaCompileOptionConstructorMapper.class ) +public interface CustomerJakartaCompileOptionConstructorMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/GenderJakartaCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/GenderJakartaCompileOptionConstructorMapper.java new file mode 100644 index 0000000000..4539bb1c69 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/GenderJakartaCompileOptionConstructorMapper.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.injectionstrategy.jakarta.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA) +public interface GenderJakartaCompileOptionConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaAndJsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaAndJsr330CompileOptionConstructorMapperTest.java new file mode 100644 index 0000000000..c897891b14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaAndJsr330CompileOptionConstructorMapperTest.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.injectionstrategy.jakarta.compileoptionconstructor; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.WithJavaxInject; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test constructor injection for component model jakarta with compile option + * mapstruct.defaultInjectionStrategy=constructor + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaCompileOptionConstructorMapper.class, + GenderJakartaCompileOptionConstructorMapper.class +}) +@ProcessorOption(name = "mapstruct.defaultInjectionStrategy", value = "constructor") +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330CompileOptionConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaCompileOptionConstructorMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private final GenderJakartaCompileOptionConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJakartaCompileOptionConstructorMapperImpl" + + "(GenderJakartaCompileOptionConstructorMapper" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaCompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaCompileOptionConstructorMapperTest.java new file mode 100644 index 0000000000..691d04ea89 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/compileoptionconstructor/JakartaCompileOptionConstructorMapperTest.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.injectionstrategy.jakarta.compileoptionconstructor; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.compilation.annotation.ProcessorOption; +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 java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test constructor injection for component model jakarta with compile option + * mapstruct.defaultInjectionStrategy=constructor + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaCompileOptionConstructorMapper.class, + GenderJakartaCompileOptionConstructorMapper.class +}) +@ProcessorOption(name = "mapstruct.defaultInjectionStrategy", value = "constructor") +@ComponentScan(basePackageClasses = CustomerJakartaCompileOptionConstructorMapper.class) +@Configuration +@WithJakartaInject +public class JakartaCompileOptionConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerJakartaCompileOptionConstructorMapper customerMapper; + 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 shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaCompileOptionConstructorMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private final GenderJakartaCompileOptionConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJakartaCompileOptionConstructorMapperImpl" + + "(GenderJakartaCompileOptionConstructorMapper" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/ConstructorJakartaConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/ConstructorJakartaConfig.java new file mode 100644 index 0000000000..0bc9bb16e6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/ConstructorJakartaConfig.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.injectionstrategy.jakarta.constructor; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.JAKARTA, + injectionStrategy = InjectionStrategy.CONSTRUCTOR) +public interface ConstructorJakartaConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/CustomerJakartaConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/CustomerJakartaConstructorMapper.java new file mode 100644 index 0000000000..73d201cbfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/CustomerJakartaConstructorMapper.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.injectionstrategy.jakarta.constructor; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JAKARTA, + uses = GenderJakartaConstructorMapper.class, + injectionStrategy = InjectionStrategy.CONSTRUCTOR ) +public interface CustomerJakartaConstructorMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/GenderJakartaConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/GenderJakartaConstructorMapper.java new file mode 100644 index 0000000000..7cbd0bb3df --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/GenderJakartaConstructorMapper.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.injectionstrategy.jakarta.constructor; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = ConstructorJakartaConfig.class) +public interface GenderJakartaConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaAndJsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaAndJsr330ConstructorMapperTest.java new file mode 100644 index 0000000000..03b5dcf2bb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaAndJsr330ConstructorMapperTest.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.injectionstrategy.jakarta.constructor; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +import static java.lang.System.lineSeparator; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaConstructorMapper.class, + GenderJakartaConstructorMapper.class, + ConstructorJakartaConfig.class +}) +@ComponentScan(basePackageClasses = CustomerJakartaConstructorMapper.class) +@Configuration +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330ConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaConstructorMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private final GenderJakartaConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJakartaConstructorMapperImpl(GenderJakartaConstructorMapper" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaConstructorMapperTest.java new file mode 100644 index 0000000000..79d33f10d2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/constructor/JakartaConstructorMapperTest.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.injectionstrategy.jakarta.constructor; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.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 java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaConstructorMapper.class, + GenderJakartaConstructorMapper.class, + ConstructorJakartaConfig.class +}) +@ComponentScan(basePackageClasses = CustomerJakartaConstructorMapper.class) +@Configuration +@WithJakartaInject +public class JakartaConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerJakartaConstructorMapper customerMapper; + 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 shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaConstructorMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private final GenderJakartaConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJakartaConstructorMapperImpl(GenderJakartaConstructorMapper" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/CustomerFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/CustomerFieldMapper.java new file mode 100644 index 0000000000..550d4ee945 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/CustomerFieldMapper.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.injectionstrategy.jakarta.field; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA, uses = GenderJakartaFieldMapper.class, + injectionStrategy = InjectionStrategy.FIELD) +public interface CustomerFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/FieldJakartaConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/FieldJakartaConfig.java new file mode 100644 index 0000000000..b3ad9474cf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/FieldJakartaConfig.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.injectionstrategy.jakarta.field; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.JAKARTA, injectionStrategy = InjectionStrategy.FIELD) +public interface FieldJakartaConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/GenderJakartaFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/GenderJakartaFieldMapper.java new file mode 100644 index 0000000000..ee5b959307 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/GenderJakartaFieldMapper.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.injectionstrategy.jakarta.field; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = FieldJakartaConfig.class) +public interface GenderJakartaFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaAndJsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaAndJsr330FieldMapperTest.java new file mode 100644 index 0000000000..56ea535571 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaAndJsr330FieldMapperTest.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.injectionstrategy.jakarta.field; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerFieldMapper.class, + GenderJakartaFieldMapper.class, + FieldJakartaConfig.class +}) +@IssueKey("2567") +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330FieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaFieldMapper" ) + .doesNotContain( "public CustomerJakartaFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaFieldMapperTest.java new file mode 100644 index 0000000000..eaae684d62 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/field/JakartaFieldMapperTest.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.injectionstrategy.jakarta.field; + +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.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerFieldMapper.class, + GenderJakartaFieldMapper.class, + FieldJakartaConfig.class +}) +@IssueKey("2567") +@ComponentScan(basePackageClasses = CustomerFieldMapper.class) +@Configuration +@WithJakartaInject +public class JakartaFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Inject + @Named + private CustomerFieldMapper customerMapper; + 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 shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaFieldMapper" ) + .doesNotContain( "public CustomerJakartaFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/CustomerJakartaSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/CustomerJakartaSetterMapper.java new file mode 100644 index 0000000000..d422442791 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/CustomerJakartaSetterMapper.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.injectionstrategy.jakarta.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JAKARTA, + uses = GenderJakartaSetterMapper.class, + injectionStrategy = InjectionStrategy.SETTER ) +public interface CustomerJakartaSetterMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/GenderJakartaSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/GenderJakartaSetterMapper.java new file mode 100644 index 0000000000..f0d9b851e9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/GenderJakartaSetterMapper.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.injectionstrategy.jakarta.setter; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = SetterJakartaConfig.class) +public interface GenderJakartaSetterMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/JakartaSetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/JakartaSetterMapperTest.java new file mode 100644 index 0000000000..0e051fa759 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/JakartaSetterMapperTest.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.injectionstrategy.jakarta.setter; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.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 java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaSetterMapper.class, + GenderJakartaSetterMapper.class, + SetterJakartaConfig.class +}) +@IssueKey("3229") +@ComponentScan(basePackageClasses = CustomerJakartaSetterMapper.class) +@Configuration +@WithJakartaInject +public class JakartaSetterMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerJakartaSetterMapper customerMapper; + 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 shouldConvertToTarget() { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + + // when + CustomerDto customerDto = customerMapper.asTarget( customerEntity ); + + // then + assertThat( customerDto ).isNotNull(); + assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); + assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); + } + + @ProcessorTest + public void shouldHaveSetterInjection() { + String method = "@Inject" + lineSeparator() + + " public void setGenderJakartaSetterMapper(GenderJakartaSetterMapper genderJakartaSetterMapper) {" + + lineSeparator() + " this.genderJakartaSetterMapper = genderJakartaSetterMapper;" + + lineSeparator() + " }"; + generatedSource.forMapper( CustomerJakartaSetterMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "private GenderJakartaSetterMapper genderJakartaSetterMapper;" ) + .doesNotContain( "@Inject" + lineSeparator() + " private GenderJakartaSetterMapper" ) + .contains( method ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/SetterJakartaConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/SetterJakartaConfig.java new file mode 100644 index 0000000000..a189244936 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta/setter/SetterJakartaConfig.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.injectionstrategy.jakarta.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.JAKARTA, + injectionStrategy = InjectionStrategy.SETTER) +public interface SetterJakartaConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/CustomerJakartaCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/CustomerJakartaCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..9ec0709f52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/CustomerJakartaCdiDefaultCompileOptionFieldMapper.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.injectionstrategy.jakarta_cdi._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA_CDI, + uses = GenderJakartaCdiDefaultCompileOptionFieldMapper.class) +public interface CustomerJakartaCdiDefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/GenderJakartaCdiDefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/GenderJakartaCdiDefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..0c1aef6ec6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/GenderJakartaCdiDefaultCompileOptionFieldMapper.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.injectionstrategy.jakarta_cdi._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +public interface GenderJakartaCdiDefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..b542c99c2a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest.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.injectionstrategy.jakarta_cdi._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithCdi; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaCdi; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaCdiDefaultCompileOptionFieldMapper.class, + GenderJakartaCdiDefaultCompileOptionFieldMapper.class +}) +@WithJakartaCdi +@WithCdi +public class JakartaCdiAndCdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJakartaCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.enterprise.context.ApplicationScoped;" ) + .contains( "import jakarta.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "javax.inject" ) + .doesNotContain( "javax.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiDefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiDefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..57714cdb01 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/_default/JakartaCdiDefaultCompileOptionFieldMapperTest.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.injectionstrategy.jakarta_cdi._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJakartaCdi; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jakarta-cdi. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2950") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJakartaCdiDefaultCompileOptionFieldMapper.class, + GenderJakartaCdiDefaultCompileOptionFieldMapper.class +}) +@WithJakartaCdi +public class JakartaCdiDefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveFieldInjection() { + generatedSource.forMapper( CustomerJakartaCdiDefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.enterprise.context.ApplicationScoped;" ) + .contains( "import jakarta.inject.Inject;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJakartaCdiDefaultCompileOptionFieldMapper" ) + .contains( "@ApplicationScoped" + lineSeparator() + "public class" ) + .doesNotContain( "public CustomerJakartaCdiDefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ) + .doesNotContain( "javax.enterprise" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/CustomerJsr330DefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/CustomerJsr330DefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..7fcbe97d5a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/CustomerJsr330DefaultCompileOptionFieldMapper.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.injectionstrategy.jsr330._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, + uses = GenderJsr330DefaultCompileOptionFieldMapper.class) +public interface CustomerJsr330DefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/GenderJsr330DefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/GenderJsr330DefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..1ecf720cbf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/GenderJsr330DefaultCompileOptionFieldMapper.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.injectionstrategy.jsr330._default; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +public interface GenderJsr330DefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..3e562ff39a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/_default/Jsr330DefaultCompileOptionFieldMapperTest.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.injectionstrategy.jsr330._default; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Andrei Arlou + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330DefaultCompileOptionFieldMapper.class, + GenderJsr330DefaultCompileOptionFieldMapper.class +}) +@WithJavaxInject +public class Jsr330DefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveFieldInjection() { + generatedSource.forMapper( CustomerJsr330DefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJsr330DefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJsr330DefaultCompileOptionFieldMapperImpl(" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/CustomerJsr330CompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/CustomerJsr330CompileOptionConstructorMapper.java new file mode 100644 index 0000000000..02aeaef088 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/CustomerJsr330CompileOptionConstructorMapper.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.injectionstrategy.jsr330.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Andrei Arlou + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JSR330, + uses = GenderJsr330CompileOptionConstructorMapper.class ) +public interface CustomerJsr330CompileOptionConstructorMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/GenderJsr330CompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/GenderJsr330CompileOptionConstructorMapper.java new file mode 100644 index 0000000000..bffcc06bb9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/GenderJsr330CompileOptionConstructorMapper.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.injectionstrategy.jsr330.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +public interface GenderJsr330CompileOptionConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.java new file mode 100644 index 0000000000..2e4a44861f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/compileoptionconstructor/Jsr330CompileOptionConstructorMapperTest.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.injectionstrategy.jsr330.compileoptionconstructor; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJavaxInject; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test constructor injection for component model jsr330 with compile option + * mapstruct.defaultInjectionStrategy=constructor + * + * @author Andrei Arlou + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330CompileOptionConstructorMapper.class, + GenderJsr330CompileOptionConstructorMapper.class +}) +@ProcessorOption( name = "mapstruct.defaultInjectionStrategy", value = "constructor") +@WithJavaxInject +public class Jsr330CompileOptionConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveConstructorInjectionFromCompileOption() { + generatedSource.forMapper( CustomerJsr330CompileOptionConstructorMapper.class ) + .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) + .contains( "private final GenderJsr330CompileOptionConstructorMapper" ) + .contains( "@Inject" + lineSeparator() + + " public CustomerJsr330CompileOptionConstructorMapperImpl" + + "(GenderJsr330CompileOptionConstructorMapper" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/ConstructorJsr330Config.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/ConstructorJsr330Config.java index 3bfc7d23ae..177b70d1dc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/ConstructorJsr330Config.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/ConstructorJsr330Config.java @@ -7,10 +7,12 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; /** * @author Filip Hrisafov */ -@MapperConfig(componentModel = "jsr330", injectionStrategy = InjectionStrategy.CONSTRUCTOR) +@MapperConfig(componentModel = MappingConstants.ComponentModel.JSR330, + injectionStrategy = InjectionStrategy.CONSTRUCTOR) public interface ConstructorJsr330Config { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/CustomerJsr330ConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/CustomerJsr330ConstructorMapper.java index 001cb9f736..32ef9d308b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/CustomerJsr330ConstructorMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/CustomerJsr330ConstructorMapper.java @@ -8,18 +8,19 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; /** * @author Kevin Grüneberg */ -@Mapper( componentModel = "jsr330", +@Mapper( componentModel = MappingConstants.ComponentModel.JSR330, uses = GenderJsr330ConstructorMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR ) public interface CustomerJsr330ConstructorMapper { - @Mapping(source = "gender", target = "gender") + @Mapping(target = "gender", source = "gender") CustomerDto asTarget(CustomerEntity customerEntity); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java index d76b077a3f..b872fc728b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/constructor/Jsr330ConstructorMapperTest.java @@ -5,27 +5,18 @@ */ package org.mapstruct.ap.test.injectionstrategy.jsr330.constructor; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; import org.mapstruct.ap.test.injectionstrategy.shared.Gender; import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; 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.WithJavaxInject; 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 java.lang.System.lineSeparator; -import static org.assertj.core.api.Assertions.assertThat; /** * Test constructor injection for component model spring. @@ -42,51 +33,19 @@ ConstructorJsr330Config.class }) @IssueKey("571") -@RunWith(AnnotationProcessorTestRunner.class) -@ComponentScan(basePackageClasses = CustomerJsr330ConstructorMapper.class) -@Configuration +@WithJavaxInject public class Jsr330ConstructorMapperTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Autowired - private CustomerJsr330ConstructorMapper customerMapper; - 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 shouldConvertToTarget() { - // given - CustomerEntity customerEntity = new CustomerEntity(); - customerEntity.setName( "Samuel" ); - customerEntity.setGender( Gender.MALE ); - - // when - CustomerDto customerDto = customerMapper.asTarget( customerEntity ); - - // then - assertThat( customerDto ).isNotNull(); - assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); - assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); - } - - @Test + @ProcessorTest public void shouldHaveConstructorInjection() { generatedSource.forMapper( CustomerJsr330ConstructorMapper.class ) .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) .contains( "private final GenderJsr330ConstructorMapper" ) .contains( "@Inject" + lineSeparator() + " public CustomerJsr330ConstructorMapperImpl(GenderJsr330ConstructorMapper" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/CustomerJsr330FieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/CustomerJsr330FieldMapper.java index 5c53a91c0f..a9e02280a6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/CustomerJsr330FieldMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/CustomerJsr330FieldMapper.java @@ -7,13 +7,15 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; /** * @author Kevin Grüneberg */ -@Mapper(componentModel = "jsr330", uses = GenderJsr330FieldMapper.class, injectionStrategy = InjectionStrategy.FIELD) +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, uses = GenderJsr330FieldMapper.class, + injectionStrategy = InjectionStrategy.FIELD) public interface CustomerJsr330FieldMapper { CustomerDto asTarget(CustomerEntity customerEntity); diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/FieldJsr330Config.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/FieldJsr330Config.java index 33b5bbe8dc..ba3938ae48 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/FieldJsr330Config.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/FieldJsr330Config.java @@ -7,10 +7,11 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; /** * @author Filip Hrisafov */ -@MapperConfig(componentModel = "jsr330", injectionStrategy = InjectionStrategy.FIELD) +@MapperConfig(componentModel = MappingConstants.ComponentModel.JSR330, injectionStrategy = InjectionStrategy.FIELD) public interface FieldJsr330Config { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java index 551821df05..9f5b31d136 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/field/Jsr330FieldMapperTest.java @@ -5,29 +5,18 @@ */ package org.mapstruct.ap.test.injectionstrategy.jsr330.field; -import javax.inject.Inject; -import javax.inject.Named; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; import org.mapstruct.ap.test.injectionstrategy.shared.Gender; import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; 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.WithJavaxInject; 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 field injection for component model spring. @@ -44,52 +33,19 @@ FieldJsr330Config.class }) @IssueKey("571") -@RunWith(AnnotationProcessorTestRunner.class) -@ComponentScan(basePackageClasses = CustomerJsr330FieldMapper.class) -@Configuration +@WithJavaxInject public class Jsr330FieldMapperTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); - - @Inject - @Named - private CustomerJsr330FieldMapper customerMapper; - 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 shouldConvertToTarget() { - // given - CustomerEntity customerEntity = new CustomerEntity(); - customerEntity.setName( "Samuel" ); - customerEntity.setGender( Gender.MALE ); - - // when - CustomerDto customerDto = customerMapper.asTarget( customerEntity ); - - // then - assertThat( customerDto ).isNotNull(); - assertThat( customerDto.getName() ).isEqualTo( "Samuel" ); - assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); - } + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest public void shouldHaveFieldInjection() { generatedSource.forMapper( CustomerJsr330FieldMapper.class ) .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) .contains( "@Inject" + lineSeparator() + " private GenderJsr330FieldMapper" ) .doesNotContain( "public CustomerJsr330FieldMapperImpl(" ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/CustomerJsr330DefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/CustomerJsr330DefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..80f5c60ef0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/CustomerJsr330DefaultCompileOptionFieldMapper.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.injectionstrategy.jsr330.jakarta; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330, + uses = GenderJsr330DefaultCompileOptionFieldMapper.class) +public interface CustomerJsr330DefaultCompileOptionFieldMapper { + + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/GenderJsr330DefaultCompileOptionFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/GenderJsr330DefaultCompileOptionFieldMapper.java new file mode 100644 index 0000000000..4a9b56c1b2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/GenderJsr330DefaultCompileOptionFieldMapper.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.injectionstrategy.jsr330.jakarta; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.JSR330) +public interface GenderJsr330DefaultCompileOptionFieldMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..da7176fc7a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaAndJsr330DefaultCompileOptionFieldMapperTest.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.injectionstrategy.jsr330.jakarta; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.WithJavaxInject; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330DefaultCompileOptionFieldMapper.class, + GenderJsr330DefaultCompileOptionFieldMapper.class +}) +@WithJakartaInject +@WithJavaxInject +public class JakartaAndJsr330DefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJavaxInjection() { + generatedSource.forMapper( CustomerJsr330DefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJsr330DefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJsr330DefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "jakarta.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaJsr330DefaultCompileOptionFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaJsr330DefaultCompileOptionFieldMapperTest.java new file mode 100644 index 0000000000..f78721fb1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/jakarta/JakartaJsr330DefaultCompileOptionFieldMapperTest.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.injectionstrategy.jsr330.jakarta; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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 static java.lang.System.lineSeparator; + +/** + * Test field injection for component model jsr330. + * Default value option mapstruct.defaultInjectionStrategy is "field" + * + * @author Filip Hrisafov + */ +@IssueKey("2567") +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330DefaultCompileOptionFieldMapper.class, + GenderJsr330DefaultCompileOptionFieldMapper.class +}) +@WithJakartaInject +public class JakartaJsr330DefaultCompileOptionFieldMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveJakartaInjection() { + generatedSource.forMapper( CustomerJsr330DefaultCompileOptionFieldMapper.class ) + .content() + .contains( "import jakarta.inject.Inject;" ) + .contains( "import jakarta.inject.Named;" ) + .contains( "import jakarta.inject.Singleton;" ) + .contains( "@Inject" + lineSeparator() + " private GenderJsr330DefaultCompileOptionFieldMapper" ) + .doesNotContain( "public CustomerJsr330DefaultCompileOptionFieldMapperImpl(" ) + .doesNotContain( "javax.inject" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/CustomerJsr330SetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/CustomerJsr330SetterMapper.java new file mode 100644 index 0000000000..ab269f83f8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/CustomerJsr330SetterMapper.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.injectionstrategy.jsr330.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Filip Hrisafov + */ +@Mapper( componentModel = MappingConstants.ComponentModel.JSR330, + uses = GenderJsr330SetterMapper.class, + injectionStrategy = InjectionStrategy.SETTER ) +public interface CustomerJsr330SetterMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/GenderJsr330SetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/GenderJsr330SetterMapper.java new file mode 100644 index 0000000000..d9b2f8ea9c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/GenderJsr330SetterMapper.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.injectionstrategy.jsr330.setter; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Filip Hrisafov + */ +@Mapper(config = SetterJsr330Config.class) +public interface GenderJsr330SetterMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.java new file mode 100644 index 0000000000..18ebdfc767 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/Jsr330SetterMapperTest.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.injectionstrategy.jsr330.setter; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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; + +import static java.lang.System.lineSeparator; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerJsr330SetterMapper.class, + GenderJsr330SetterMapper.class, + SetterJsr330Config.class +}) +@IssueKey("3229") +@WithJavaxInject +public class Jsr330SetterMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveSetterInjection() { + String method = "@Inject" + lineSeparator() + + " public void setGenderJsr330SetterMapper(GenderJsr330SetterMapper genderJsr330SetterMapper) {" + + lineSeparator() + " this.genderJsr330SetterMapper = genderJsr330SetterMapper;" + + lineSeparator() + " }"; + generatedSource.forMapper( CustomerJsr330SetterMapper.class ) + .content() + .contains( "import javax.inject.Inject;" ) + .contains( "import javax.inject.Named;" ) + .contains( "import javax.inject.Singleton;" ) + .contains( "private GenderJsr330SetterMapper genderJsr330SetterMapper;" ) + .doesNotContain( "@Inject" + lineSeparator() + " private GenderJsr330SetterMapper" ) + .contains( method ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/SetterJsr330Config.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/SetterJsr330Config.java new file mode 100644 index 0000000000..2b13a56feb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/jsr330/setter/SetterJsr330Config.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.injectionstrategy.jsr330.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Filip Hrisafov + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.JSR330, + injectionStrategy = InjectionStrategy.SETTER) +public interface SetterJsr330Config { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/CustomerSpringDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/CustomerSpringDefaultMapper.java index 34b06fc788..c7c2243d59 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/CustomerSpringDefaultMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/CustomerSpringDefaultMapper.java @@ -6,13 +6,14 @@ package org.mapstruct.ap.test.injectionstrategy.spring._default; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; /** * @author Filip Hrisafov */ -@Mapper(componentModel = "spring", uses = GenderSpringDefaultMapper.class ) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = GenderSpringDefaultMapper.class ) public interface CustomerSpringDefaultMapper { CustomerDto asTarget(CustomerEntity customerEntity); diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/GenderSpringDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/GenderSpringDefaultMapper.java index 106b93519e..e49a820994 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/GenderSpringDefaultMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/GenderSpringDefaultMapper.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.injectionstrategy.spring._default; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.ValueMapping; import org.mapstruct.ValueMappings; import org.mapstruct.ap.test.injectionstrategy.shared.Gender; @@ -14,7 +15,7 @@ /** * @author Filip Hrisafov */ -@Mapper(componentModel = "spring") +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public interface GenderSpringDefaultMapper { @ValueMappings({ diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/SpringDefaultMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/SpringDefaultMapperTest.java index c695214937..2d9c374721 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/SpringDefaultMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/_default/SpringDefaultMapperTest.java @@ -5,18 +5,17 @@ */ package org.mapstruct.ap.test.injectionstrategy.spring._default; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; import org.mapstruct.ap.test.injectionstrategy.shared.Gender; import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; 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.WithSpring; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; @@ -41,32 +40,32 @@ GenderSpringDefaultMapper.class }) @IssueKey("571") -@RunWith(AnnotationProcessorTestRunner.class) @ComponentScan(basePackageClasses = CustomerSpringDefaultMapper.class) @Configuration +@WithSpring public class SpringDefaultMapperTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); @Autowired private CustomerSpringDefaultMapper customerMapper; private ConfigurableApplicationContext context; - @Before + @BeforeEach public void springUp() { context = new AnnotationConfigApplicationContext( getClass() ); context.getAutowireCapableBeanFactory().autowireBean( this ); } - @After + @AfterEach public void springDown() { if ( context != null ) { context.close(); } } - @Test + @ProcessorTest public void shouldConvertToTarget() { // given CustomerEntity customerEntity = new CustomerEntity(); @@ -82,7 +81,7 @@ public void shouldConvertToTarget() { assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); } - @Test + @ProcessorTest public void shouldHaveFieldInjection() { generatedSource.forMapper( CustomerSpringDefaultMapper.class ) .content() diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.java new file mode 100644 index 0000000000..ccc196c5d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomStereotype.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.injectionstrategy.spring.annotateWith; + +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 org.springframework.stereotype.Component; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Component +public @interface CustomStereotype { + String value() default ""; +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.java new file mode 100644 index 0000000000..be08ff0b1c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringComponentQualifiedMapper.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.injectionstrategy.spring.annotateWith; + +import org.springframework.stereotype.Component; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; + +/** + * @author Ben Zegveld + */ +@AnnotateWith( value = Component.class, elements = @AnnotateWith.Element( strings = "AnnotateWithComponent" ) ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringComponentQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.java new file mode 100644 index 0000000000..7203fad7a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringControllerQualifiedMapper.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.injectionstrategy.spring.annotateWith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.stereotype.Controller; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@AnnotateWith( value = Controller.class, elements = @AnnotateWith.Element( strings = "AnnotateWithController" ) ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringControllerQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.java new file mode 100644 index 0000000000..18a062497d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringCustomStereotypeQualifiedMapper.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.injectionstrategy.spring.annotateWith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@AnnotateWith( + value = CustomStereotype.class, + elements = @AnnotateWith.Element( strings = "AnnotateWithCustomStereotype" ) +) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringCustomStereotypeQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.java new file mode 100644 index 0000000000..7bbefbee2b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringRepositoryQualifiedMapper.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.injectionstrategy.spring.annotateWith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.stereotype.Repository; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@AnnotateWith( value = Repository.class, elements = @AnnotateWith.Element( strings = "AnnotateWithRepository" ) ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringRepositoryQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.java new file mode 100644 index 0000000000..52dff8ef8f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/CustomerSpringServiceQualifiedMapper.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.injectionstrategy.spring.annotateWith; + +import org.mapstruct.AnnotateWith; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.stereotype.Service; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@AnnotateWith( value = Service.class, elements = @AnnotateWith.Element( strings = "AnnotateWithService" ) ) +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING ) +public interface CustomerSpringServiceQualifiedMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.java new file mode 100644 index 0000000000..cdc741814c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/annotateWith/SpringAnnotateWithMapperTest.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.injectionstrategy.spring.annotateWith; + +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.WithSpring; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +/** + * Test field injection for component model spring. + * + * @author Filip Hrisafov + * @author Jose Carlos Campanero Ortiz + */ +@WithClasses({ + CustomerSpringComponentQualifiedMapper.class, + CustomerSpringControllerQualifiedMapper.class, + CustomerSpringServiceQualifiedMapper.class, + CustomerSpringRepositoryQualifiedMapper.class, + CustomStereotype.class, + CustomerSpringCustomStereotypeQualifiedMapper.class +}) +@IssueKey( "1427" ) +@WithSpring +public class SpringAnnotateWithMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + public void shouldHaveComponentAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringComponentQualifiedMapper.class ) + .content() + .contains( "@Component(value = \"AnnotateWithComponent\")" ) + .doesNotContain( "@Component" + System.lineSeparator() ); + + } + + @ProcessorTest + public void shouldHaveControllerAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringControllerQualifiedMapper.class ) + .content() + .contains( "@Controller(value = \"AnnotateWithController\")" ) + .doesNotContain( "@Component" ); + + } + + @ProcessorTest + public void shouldHaveServiceAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringServiceQualifiedMapper.class ) + .content() + .contains( "@Service(value = \"AnnotateWithService\")" ) + .doesNotContain( "@Component" ); + + } + + @ProcessorTest + public void shouldHaveRepositoryAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringRepositoryQualifiedMapper.class ) + .content() + .contains( "@Repository(value = \"AnnotateWithRepository\")" ) + .doesNotContain( "@Component" ); + + } + + @ProcessorTest + public void shouldHaveCustomStereotypeAnnotatedQualifiedMapper() { + + // then + generatedSource.forMapper( CustomerSpringCustomStereotypeQualifiedMapper.class ) + .content() + .contains( "@CustomStereotype(value = \"AnnotateWithCustomStereotype\")" ) + .doesNotContain( "@Component" ); + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerRecordSpringCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerRecordSpringCompileOptionConstructorMapper.java new file mode 100644 index 0000000000..f411ba506f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerRecordSpringCompileOptionConstructorMapper.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.injectionstrategy.spring.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = { CustomerSpringCompileOptionConstructorMapper.class, GenderSpringCompileOptionConstructorMapper.class }, + disableSubMappingMethodsGeneration = true) +public interface CustomerRecordSpringCompileOptionConstructorMapper { + + CustomerRecordDto asTarget(CustomerRecordEntity customerRecordEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerSpringCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerSpringCompileOptionConstructorMapper.java new file mode 100644 index 0000000000..3d28e3e49e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/CustomerSpringCompileOptionConstructorMapper.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.injectionstrategy.spring.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Andrei Arlou + */ +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, + uses = GenderSpringCompileOptionConstructorMapper.class) +public interface CustomerSpringCompileOptionConstructorMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/GenderSpringCompileOptionConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/GenderSpringCompileOptionConstructorMapper.java new file mode 100644 index 0000000000..096fe91b96 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/GenderSpringCompileOptionConstructorMapper.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.injectionstrategy.spring.compileoptionconstructor; + +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Andrei Arlou + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public interface GenderSpringCompileOptionConstructorMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.java new file mode 100644 index 0000000000..c3291a5f3b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/compileoptionconstructor/SpringCompileOptionConstructorMapperTest.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.injectionstrategy.spring.compileoptionconstructor; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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.ProcessorOption; +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 java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test constructor injection for component model spring with + * compile option mapstruct.defaultInjectStrategy=constructor + * + * @author Andrei Arlou + */ +@WithClasses( { + CustomerRecordDto.class, + CustomerRecordEntity.class, + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerRecordSpringCompileOptionConstructorMapper.class, + CustomerSpringCompileOptionConstructorMapper.class, + GenderSpringCompileOptionConstructorMapper.class +} ) +@ProcessorOption( name = "mapstruct.defaultInjectionStrategy", value = "constructor") +@ComponentScan(basePackageClasses = CustomerSpringCompileOptionConstructorMapper.class) +@Configuration +@WithSpring +@DefaultTimeZone("Europe/Berlin") +public class SpringCompileOptionConstructorMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerRecordSpringCompileOptionConstructorMapper customerRecordMapper; + 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 shouldConvertToTarget() throws Exception { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + CustomerRecordEntity customerRecordEntity = new CustomerRecordEntity(); + customerRecordEntity.setCustomer( customerEntity ); + customerRecordEntity.setRegistrationDate( createDate( "31-08-1982 10:20:56" ) ); + + // when + CustomerRecordDto customerRecordDto = customerRecordMapper.asTarget( customerRecordEntity ); + + // then + assertThat( customerRecordDto ).isNotNull(); + assertThat( customerRecordDto.getCustomer() ).isNotNull(); + assertThat( customerRecordDto.getCustomer().getName() ).isEqualTo( "Samuel" ); + assertThat( customerRecordDto.getCustomer().getGender() ).isEqualTo( GenderDto.M ); + assertThat( customerRecordDto.getRegistrationDate() ).isNotNull(); + assertThat( customerRecordDto.getRegistrationDate().toString() ).isEqualTo( "1982-08-31T10:20:56.000+02:00" ); + } + + private Date createDate(String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); + return sdf.parse( date ); + } + + @ProcessorTest + public void shouldConstructorInjectionFromCompileOption() { + generatedSource.forMapper( CustomerSpringCompileOptionConstructorMapper.class ) + .content() + .contains( "private final GenderSpringCompileOptionConstructorMapper" ) + .contains( "@Autowired" + lineSeparator() + + " public CustomerSpringCompileOptionConstructorMapperImpl" + + "(GenderSpringCompileOptionConstructorMapper" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/ConstructorSpringConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/ConstructorSpringConfig.java index f4b46e35c3..0bb89895cb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/ConstructorSpringConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/ConstructorSpringConfig.java @@ -7,10 +7,12 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; /** * @author Filip Hrisafov */ -@MapperConfig(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR) +@MapperConfig(componentModel = MappingConstants.ComponentModel.SPRING, + injectionStrategy = InjectionStrategy.CONSTRUCTOR) public interface ConstructorSpringConfig { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerRecordSpringConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerRecordSpringConstructorMapper.java index a7f9e8a8b2..c2a5862978 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerRecordSpringConstructorMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerRecordSpringConstructorMapper.java @@ -7,13 +7,14 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; /** * @author Kevin Grüneberg */ -@Mapper(componentModel = "spring", +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { CustomerSpringConstructorMapper.class, GenderSpringConstructorMapper.class }, injectionStrategy = InjectionStrategy.CONSTRUCTOR, disableSubMappingMethodsGeneration = true) diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerSpringConstructorMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerSpringConstructorMapper.java index 9a9ab0119a..ef7120585d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerSpringConstructorMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/CustomerSpringConstructorMapper.java @@ -8,17 +8,18 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; /** * @author Kevin Grüneberg */ -@Mapper( componentModel = "spring", +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, uses = GenderSpringConstructorMapper.class, injectionStrategy = InjectionStrategy.CONSTRUCTOR ) public interface CustomerSpringConstructorMapper { - @Mapping( source = "gender", target = "gender" ) + @Mapping(target = "gender", source = "gender") CustomerDto asTarget(CustomerEntity customerEntity); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java index 267ad8dd33..2594d5c23d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/constructor/SpringConstructorMapperTest.java @@ -10,13 +10,10 @@ import java.util.Date; import java.util.TimeZone; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; @@ -24,8 +21,9 @@ import org.mapstruct.ap.test.injectionstrategy.shared.Gender; import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; 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.WithSpring; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; @@ -54,45 +52,35 @@ ConstructorSpringConfig.class } ) @IssueKey( "571" ) -@RunWith(AnnotationProcessorTestRunner.class) @ComponentScan(basePackageClasses = CustomerSpringConstructorMapper.class) @Configuration +@WithSpring +@DefaultTimeZone("Europe/Berlin") public class SpringConstructorMapperTest { private static TimeZone originalTimeZone; - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); @Autowired private CustomerRecordSpringConstructorMapper customerRecordMapper; private ConfigurableApplicationContext context; - @BeforeClass - public static void setDefaultTimeZoneToCet() { - originalTimeZone = TimeZone.getDefault(); - TimeZone.setDefault( TimeZone.getTimeZone( "Europe/Berlin" ) ); - } - - @AfterClass - public static void restoreOriginalTimeZone() { - TimeZone.setDefault( originalTimeZone ); - } - - @Before + @BeforeEach public void springUp() { context = new AnnotationConfigApplicationContext( getClass() ); context.getAutowireCapableBeanFactory().autowireBean( this ); } - @After + @AfterEach public void springDown() { if ( context != null ) { context.close(); } } - @Test + @ProcessorTest public void shouldConvertToTarget() throws Exception { // given CustomerEntity customerEntity = new CustomerEntity(); @@ -114,7 +102,7 @@ public void shouldConvertToTarget() throws Exception { assertThat( customerRecordDto.getRegistrationDate().toString() ).isEqualTo( "1982-08-31T10:20:56.000+02:00" ); } - @Test + @ProcessorTest public void shouldHaveConstructorInjection() { generatedSource.forMapper( CustomerSpringConstructorMapper.class ) .content() diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/CustomerSpringFieldMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/CustomerSpringFieldMapper.java index 449cc5e435..fb8a316c59 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/CustomerSpringFieldMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/CustomerSpringFieldMapper.java @@ -7,13 +7,15 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; /** * @author Kevin Grüneberg */ -@Mapper(componentModel = "spring", uses = GenderSpringFieldMapper.class, injectionStrategy = InjectionStrategy.FIELD) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = GenderSpringFieldMapper.class, + injectionStrategy = InjectionStrategy.FIELD) public interface CustomerSpringFieldMapper { CustomerDto asTarget(CustomerEntity customerEntity); diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/FieldSpringConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/FieldSpringConfig.java index d44a54c935..c9e3c3f0f7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/FieldSpringConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/FieldSpringConfig.java @@ -7,10 +7,11 @@ import org.mapstruct.InjectionStrategy; import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; /** * @author Filip Hrisafov */ -@MapperConfig(componentModel = "spring", injectionStrategy = InjectionStrategy.FIELD) +@MapperConfig(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.FIELD) public interface FieldSpringConfig { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java index 12cb9577dc..d464dd5b16 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/field/SpringFieldMapperTest.java @@ -5,18 +5,17 @@ */ package org.mapstruct.ap.test.injectionstrategy.spring.field; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; import org.mapstruct.ap.test.injectionstrategy.shared.Gender; import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; 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.WithSpring; import org.mapstruct.ap.testutil.runner.GeneratedSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; @@ -42,32 +41,32 @@ FieldSpringConfig.class }) @IssueKey("571") -@RunWith(AnnotationProcessorTestRunner.class) @ComponentScan(basePackageClasses = CustomerSpringFieldMapper.class) @Configuration +@WithSpring public class SpringFieldMapperTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); @Autowired private CustomerSpringFieldMapper customerMapper; private ConfigurableApplicationContext context; - @Before + @BeforeEach public void springUp() { context = new AnnotationConfigApplicationContext( getClass() ); context.getAutowireCapableBeanFactory().autowireBean( this ); } - @After + @AfterEach public void springDown() { if ( context != null ) { context.close(); } } - @Test + @ProcessorTest public void shouldConvertToTarget() { // given CustomerEntity customerEntity = new CustomerEntity(); @@ -83,7 +82,7 @@ public void shouldConvertToTarget() { assertThat( customerDto.getGender() ).isEqualTo( GenderDto.M ); } - @Test + @ProcessorTest public void shouldHaveFieldInjection() { generatedSource.forMapper( CustomerSpringFieldMapper.class ) .content() diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerRecordSpringSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerRecordSpringSetterMapper.java new file mode 100644 index 0000000000..1d09bdda03 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerRecordSpringSetterMapper.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.injectionstrategy.spring.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; + +/** + * @author Lucas Resch + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = { CustomerSpringSetterMapper.class, GenderSpringSetterMapper.class }, + injectionStrategy = InjectionStrategy.SETTER) +public interface CustomerRecordSpringSetterMapper { + + CustomerRecordDto asTarget(CustomerRecordEntity customerRecordEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerSpringSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerSpringSetterMapper.java new file mode 100644 index 0000000000..67dbab5e9c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/CustomerSpringSetterMapper.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.injectionstrategy.spring.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; + +/** + * @author Lucas Resch + */ +@Mapper( componentModel = MappingConstants.ComponentModel.SPRING, + uses = GenderSpringSetterMapper.class, + injectionStrategy = InjectionStrategy.SETTER ) +public interface CustomerSpringSetterMapper { + + @Mapping(target = "gender", source = "gender") + CustomerDto asTarget(CustomerEntity customerEntity); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/GenderSpringSetterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/GenderSpringSetterMapper.java new file mode 100644 index 0000000000..6243465ab5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/GenderSpringSetterMapper.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.injectionstrategy.spring.setter; + +import org.mapstruct.Mapper; +import org.mapstruct.ValueMapping; +import org.mapstruct.ValueMappings; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; + +/** + * @author Lucas Resch + */ +@Mapper(config = SetterSpringConfig.class) +public interface GenderSpringSetterMapper { + + @ValueMappings({ + @ValueMapping(source = "MALE", target = "M"), + @ValueMapping(source = "FEMALE", target = "F") + }) + GenderDto mapToDto(Gender gender); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SetterSpringConfig.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SetterSpringConfig.java new file mode 100644 index 0000000000..18227f315a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SetterSpringConfig.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.injectionstrategy.spring.setter; + +import org.mapstruct.InjectionStrategy; +import org.mapstruct.MapperConfig; +import org.mapstruct.MappingConstants; + +/** + * @author Lucas Resch + */ +@MapperConfig(componentModel = MappingConstants.ComponentModel.SPRING, injectionStrategy = InjectionStrategy.SETTER) +public interface SetterSpringConfig { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SpringSetterMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SpringSetterMapperTest.java new file mode 100644 index 0000000000..4dcf098a9e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/injectionstrategy/spring/setter/SpringSetterMapperTest.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.test.injectionstrategy.spring.setter; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junitpioneer.jupiter.DefaultTimeZone; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordDto; +import org.mapstruct.ap.test.injectionstrategy.shared.CustomerRecordEntity; +import org.mapstruct.ap.test.injectionstrategy.shared.Gender; +import org.mapstruct.ap.test.injectionstrategy.shared.GenderDto; +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 java.lang.System.lineSeparator; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test setter injection for component model spring. + * + * @author Lucas Resch + */ +@WithClasses( { + CustomerRecordDto.class, + CustomerRecordEntity.class, + CustomerDto.class, + CustomerEntity.class, + Gender.class, + GenderDto.class, + CustomerRecordSpringSetterMapper.class, + CustomerSpringSetterMapper.class, + GenderSpringSetterMapper.class, + SetterSpringConfig.class +} ) +@IssueKey( "3229" ) +@ComponentScan(basePackageClasses = CustomerSpringSetterMapper.class) +@Configuration +@WithSpring +@DefaultTimeZone("Europe/Berlin") +public class SpringSetterMapperTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @Autowired + private CustomerRecordSpringSetterMapper customerRecordMapper; + 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 shouldConvertToTarget() throws Exception { + // given + CustomerEntity customerEntity = new CustomerEntity(); + customerEntity.setName( "Samuel" ); + customerEntity.setGender( Gender.MALE ); + CustomerRecordEntity customerRecordEntity = new CustomerRecordEntity(); + customerRecordEntity.setCustomer( customerEntity ); + customerRecordEntity.setRegistrationDate( createDate( "31-08-1982 10:20:56" ) ); + + // when + CustomerRecordDto customerRecordDto = customerRecordMapper.asTarget( customerRecordEntity ); + + // then + assertThat( customerRecordDto ).isNotNull(); + assertThat( customerRecordDto.getCustomer() ).isNotNull(); + assertThat( customerRecordDto.getCustomer().getName() ).isEqualTo( "Samuel" ); + assertThat( customerRecordDto.getCustomer().getGender() ).isEqualTo( GenderDto.M ); + assertThat( customerRecordDto.getRegistrationDate() ).isNotNull(); + assertThat( customerRecordDto.getRegistrationDate() ).hasToString( "1982-08-31T10:20:56.000+02:00" ); + } + + @ProcessorTest + public void shouldHaveSetterInjection() { + String method = "@Autowired" + lineSeparator() + + " public void setGenderSpringSetterMapper(GenderSpringSetterMapper genderSpringSetterMapper) {" + + lineSeparator() + " this.genderSpringSetterMapper = genderSpringSetterMapper;" + + lineSeparator() + " }"; + generatedSource.forMapper( CustomerSpringSetterMapper.class ) + .content() + .contains( "private GenderSpringSetterMapper genderSpringSetterMapper;" ) + .contains( method ); + } + + private Date createDate(String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat( "dd-M-yyyy hh:mm:ss" ); + return sdf.parse( date ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/SourceTargetMapper.java index 8dc5c93484..86d83844e8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/SourceTargetMapper.java @@ -22,13 +22,13 @@ public abstract class SourceTargetMapper { static final SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); @Mappings({ - @Mapping(source = "stringStream", target = "stringList"), - @Mapping(source = "stringArrayStream", target = "stringArrayList"), - @Mapping(source = "stringStreamToSet", target = "stringSet"), - @Mapping(source = "integerStream", target = "integerCollection"), - @Mapping(source = "anotherIntegerStream", target = "anotherStringSet"), - @Mapping(source = "stringStream2", target = "stringListNoSetter"), - @Mapping(source = "stringStream3", target = "nonGenericStringList") + @Mapping(target = "stringList", source = "stringStream"), + @Mapping(target = "stringArrayList", source = "stringArrayStream"), + @Mapping(target = "stringSet", source = "stringStreamToSet"), + @Mapping(target = "integerCollection", source = "integerStream"), + @Mapping(target = "anotherStringSet", source = "anotherIntegerStream"), + @Mapping(target = "stringListNoSetter", source = "stringStream2"), + @Mapping(target = "nonGenericStringList", source = "stringStream3") }) public abstract Target sourceToTarget(Source source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/StreamMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/StreamMappingTest.java index 4fa628502e..a15468e5e1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/StreamMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/StreamMappingTest.java @@ -5,19 +5,17 @@ */ package org.mapstruct.ap.test.java8stream; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.ArrayList; import java.util.Arrays; -import java.util.EnumSet; import java.util.HashSet; import java.util.Set; +import java.util.stream.Stream; -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; @WithClasses({ Source.class, @@ -29,10 +27,9 @@ StringHolder.class }) @IssueKey( "962" ) -@RunWith(AnnotationProcessorTestRunner.class) public class StreamMappingTest { - @Test + @ProcessorTest public void shouldMapNullList() { Source source = new Source(); @@ -42,7 +39,7 @@ public void shouldMapNullList() { assertThat( target.getStringList() ).isNull(); } - @Test + @ProcessorTest public void shouldReverseMapNullList() { Target target = new Target(); @@ -52,10 +49,10 @@ public void shouldReverseMapNullList() { assertThat( source.getStringStream() ).isNull(); } - @Test + @ProcessorTest public void shouldMapList() { Source source = new Source(); - source.setStringStream( Arrays.asList( "Bob", "Alice" ).stream() ); + source.setStringStream( Stream.of( "Bob", "Alice" ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -63,10 +60,10 @@ public void shouldMapList() { assertThat( target.getStringList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest public void shouldMapListWithoutSetter() { Source source = new Source(); - source.setStringStream2( Arrays.asList( "Bob", "Alice" ).stream() ); + source.setStringStream2( Stream.of( "Bob", "Alice" ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -74,7 +71,7 @@ public void shouldMapListWithoutSetter() { assertThat( target.getStringListNoSetter() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest public void shouldReverseMapList() { Target target = new Target(); target.setStringList( Arrays.asList( "Bob", "Alice" ) ); @@ -85,10 +82,10 @@ public void shouldReverseMapList() { assertThat( source.getStringStream() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest public void shouldMapArrayList() { Source source = new Source(); - source.setStringArrayStream( new ArrayList( Arrays.asList( "Bob", "Alice" ) ).stream() ); + source.setStringArrayStream( new ArrayList<>( Arrays.asList( "Bob", "Alice" ) ).stream() ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -96,10 +93,10 @@ public void shouldMapArrayList() { assertThat( target.getStringArrayList() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest 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 ); @@ -107,10 +104,10 @@ public void shouldReverseMapArrayList() { assertThat( source.getStringArrayStream() ).containsExactly( "Bob", "Alice" ); } - @Test + @ProcessorTest public void shouldMapSet() { Source source = new Source(); - source.setStringStreamToSet( new HashSet( Arrays.asList( "Bob", "Alice" ) ).stream() ); + source.setStringStreamToSet( new HashSet<>( Arrays.asList( "Bob", "Alice" ) ).stream() ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -118,10 +115,10 @@ public void shouldMapSet() { assertThat( target.getStringSet() ).contains( "Bob", "Alice" ); } - @Test + @ProcessorTest 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 ); @@ -129,10 +126,10 @@ public void shouldReverseMapSet() { assertThat( source.getStringStreamToSet() ).contains( "Bob", "Alice" ); } - @Test + @ProcessorTest public void shouldMapListToCollection() { Source source = new Source(); - source.setIntegerStream( Arrays.asList( 1, 2 ).stream() ); + source.setIntegerStream( Stream.of( 1, 2 ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -140,7 +137,7 @@ public void shouldMapListToCollection() { assertThat( target.getIntegerCollection() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest public void shouldReverseMapListToCollection() { Target target = new Target(); target.setIntegerCollection( Arrays.asList( 1, 2 ) ); @@ -151,10 +148,10 @@ public void shouldReverseMapListToCollection() { assertThat( source.getIntegerStream() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest public void shouldMapIntegerSetToStringSet() { Source source = new Source(); - source.setAnotherIntegerStream( new HashSet( Arrays.asList( 1, 2 ) ).stream() ); + source.setAnotherIntegerStream( new HashSet<>( Arrays.asList( 1, 2 ) ).stream() ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -162,10 +159,10 @@ public void shouldMapIntegerSetToStringSet() { assertThat( target.getAnotherStringSet() ).containsOnly( "1", "2" ); } - @Test + @ProcessorTest 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 ); @@ -173,10 +170,10 @@ public void shouldReverseMapIntegerSetToStringSet() { assertThat( source.getAnotherIntegerStream() ).containsOnly( 1, 2 ); } - @Test + @ProcessorTest public void shouldMapSetOfEnumToStringSet() { Source source = new Source(); - source.setColours( EnumSet.of( Colour.BLUE, Colour.GREEN ).stream() ); + source.setColours( Stream.of( Colour.BLUE, Colour.GREEN ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); @@ -184,10 +181,10 @@ public void shouldMapSetOfEnumToStringSet() { assertThat( target.getColours() ).containsOnly( "BLUE", "GREEN" ); } - @Test + @ProcessorTest 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 ); @@ -195,19 +192,19 @@ public void shouldReverseMapSetOfEnumToStringSet() { assertThat( source.getColours() ).containsOnly( Colour.GREEN, Colour.BLUE ); } - @Test + @ProcessorTest public void shouldMapIntegerStreamToNumberSet() { Set numbers = SourceTargetMapper.INSTANCE - .integerStreamToNumberSet( Arrays.asList( 123, 456 ).stream() ); + .integerStreamToNumberSet( Stream.of( 123, 456 ) ); assertThat( numbers ).isNotNull(); assertThat( numbers ).containsOnly( 123, 456 ); } - @Test + @ProcessorTest public void shouldMapNonGenericList() { Source source = new Source(); - source.setStringStream3( new ArrayList( Arrays.asList( "Bob", "Alice" ) ).stream() ); + source.setStringStream3( new ArrayList<>( Arrays.asList( "Bob", "Alice" ) ).stream() ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/StringHolder.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/StringHolder.java index c82e1d0142..d1fff8cbf5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/StringHolder.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/StringHolder.java @@ -41,13 +41,10 @@ public boolean equals(Object obj) { } StringHolder other = (StringHolder) obj; if ( string == null ) { - if ( other.string != null ) { - return false; - } + return other.string == null; } - else if ( !string.equals( other.string ) ) { - return false; + else { + return string.equals( other.string ); } - return true; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/Target.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/Target.java index 2d63c3f166..49b23530df 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/Target.java @@ -77,7 +77,7 @@ public Set getColours() { public List getStringListNoSetter() { if ( stringListNoSetter == null ) { - stringListNoSetter = new ArrayList(); + stringListNoSetter = new ArrayList<>(); } return stringListNoSetter; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/base/StreamMapper.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/base/StreamMapper.java index edd6b87d19..71a582845c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/base/StreamMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/base/StreamMapper.java @@ -21,8 +21,8 @@ public interface StreamMapper { StreamMapper INSTANCE = Mappers.getMapper( StreamMapper.class ); @Mappings( { - @Mapping( source = "stream", target = "targetStream"), - @Mapping( source = "sourceElements", target = "targetElements") + @Mapping(target = "targetStream", source = "stream"), + @Mapping(target = "targetElements", source = "sourceElements") } ) Target map(Source source); diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/base/StreamsTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/base/StreamsTest.java index 5f6f74d1b6..027f619c98 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/base/StreamsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/base/StreamsTest.java @@ -10,13 +10,11 @@ import java.util.TreeSet; import java.util.stream.Stream; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; 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.runner.AnnotationProcessorTestRunner; import org.mapstruct.ap.testutil.runner.GeneratedSource; import static org.assertj.core.api.Assertions.assertThat; @@ -25,7 +23,6 @@ * @author Filip Hrisafov */ @IssueKey("962") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Source.class, Target.class, @@ -36,36 +33,36 @@ }) public class StreamsTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test - public void shouldNotContainFunctionIdentity() throws Exception { + @ProcessorTest + public void shouldNotContainFunctionIdentity() { generatedSource.forMapper( StreamMapper.class ) .content() .as( "The Mapper implementation should not use Function.identity()" ) .doesNotContain( "Function.identity()" ); } - @Test - public void shouldMapSourceStream() throws Exception { + @ProcessorTest + public void shouldMapSourceStream() { List someInts = Arrays.asList( 1, 2, 3 ); Stream stream = someInts.stream(); Source source = new Source(); source.setStream( stream ); - source.setStringStream( Arrays.asList( "4", "5", "6", "7" ).stream() ); + source.setStringStream( Stream.of( "4", "5", "6", "7" ) ); source.setInts( Arrays.asList( 1, 2, 3 ) ); - source.setIntegerSet( Arrays.asList( 1, 1, 2, 2, 4, 4 ).stream() ); - source.setStringCollection( Arrays.asList( "1", "1", "2", "3" ).stream().distinct() ); - source.setIntegerIterable( Arrays.asList( 10, 11, 12 ).stream() ); - source.setSortedSet( Arrays.asList( 12, 11, 10 ).stream() ); - source.setNavigableSet( Arrays.asList( 12, 11, 10 ).stream() ); - source.setIntToStringStream( Arrays.asList( 10, 11, 12 ).stream() ); - source.setStringArrayStream( Arrays.asList( "4", "5", "6", "6" ).stream().limit( 2 ) ); + source.setIntegerSet( Stream.of( 1, 1, 2, 2, 4, 4 ) ); + source.setStringCollection( Stream.of( "1", "1", "2", "3" ).distinct() ); + source.setIntegerIterable( Stream.of( 10, 11, 12 ) ); + source.setSortedSet( Stream.of( 12, 11, 10 ) ); + source.setNavigableSet( Stream.of( 12, 11, 10 ) ); + source.setIntToStringStream( Stream.of( 10, 11, 12 ) ); + source.setStringArrayStream( Stream.of( "4", "5", "6", "6" ).limit( 2 ) ); SourceElement element = new SourceElement(); element.setSource( "source1" ); - source.setSourceElements( Arrays.asList( element ).stream() ); + source.setSourceElements( Stream.of( element ) ); Target target = StreamMapper.INSTANCE.map( source ); @@ -84,19 +81,19 @@ public void shouldMapSourceStream() throws Exception { assertThat( target.getTargetElements().get( 0 ).getSource() ).isEqualTo( "source1" ); } - @Test - public void shouldMapTargetStream() throws Exception { + @ProcessorTest + public void shouldMapTargetStream() { List someInts = Arrays.asList( 1, 2, 3 ); Stream stream = someInts.stream(); Target target = new Target(); target.setTargetStream( stream ); target.setStringStream( Arrays.asList( "4", "5", "6", "7" ) ); - target.setInts( Arrays.asList( 1, 2, 3 ).stream() ); + target.setInts( Stream.of( 1, 2, 3 ) ); target.setIntegerSet( Collections.asSet( 1, 1, 2, 2, 4, 4 ) ); target.setStringCollection( Collections.asSet( "1", "1", "2", "3" ) ); target.setIntegerIterable( Arrays.asList( 10, 11, 12 ) ); - target.setSortedSet( new TreeSet( Arrays.asList( 12, 11, 10 ) ) ); - target.setNavigableSet( new TreeSet( Arrays.asList( 12, 11, 10 ) ) ); + target.setSortedSet( new TreeSet<>( Arrays.asList( 12, 11, 10 ) ) ); + target.setNavigableSet( new TreeSet<>( Arrays.asList( 12, 11, 10 ) ) ); target.setIntToStringStream( Arrays.asList( "4", "5", "6" ) ); target.setStringArrayStream( new Integer[] { 10, 11, 12 } ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/context/StreamWithContextTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/context/StreamWithContextTest.java index 9800ef75df..74fbeb10b8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/context/StreamWithContextTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/context/StreamWithContextTest.java @@ -5,15 +5,12 @@ */ package org.mapstruct.ap.test.java8stream.context; -import java.util.Arrays; import java.util.Collection; import java.util.stream.Stream; -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; @@ -22,29 +19,28 @@ */ @WithClasses({ StreamContext.class, StreamWithContextMapper.class }) @IssueKey("962") -@RunWith(AnnotationProcessorTestRunner.class) public class StreamWithContextTest { - @Test - public void shouldApplyAfterMapping() throws Exception { + @ProcessorTest + public void shouldApplyAfterMapping() { Stream stringStream = StreamWithContextMapper.INSTANCE.intStreamToStringStream( - Arrays.asList( 1, 2, 3, 5 ).stream() ); + Stream.of( 1, 2, 3, 5 ) ); assertThat( stringStream ).containsOnly( "1", "2" ); } - @Test - public void shouldApplyBeforeMappingOnArray() throws Exception { + @ProcessorTest + public void shouldApplyBeforeMappingOnArray() { Integer[] integers = new Integer[] { 1, 3 }; Stream stringStream = StreamWithContextMapper.INSTANCE.arrayToStream( integers ); assertThat( stringStream ).containsOnly( 30, 3 ); } - @Test - public void shouldApplyBeforeAndAfterMappingOnCollection() throws Exception { + @ProcessorTest + public void shouldApplyBeforeAndAfterMappingOnCollection() { Collection stringsStream = StreamWithContextMapper.INSTANCE.streamToCollection( - Arrays.asList( 10, 20, 40 ).stream() ); + Stream.of( 10, 20, 40 ) ); assertThat( stringsStream ).containsOnly( "23", "10", "20", "40", "230" ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/DefaultStreamImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/DefaultStreamImplementationTest.java index 441d3d3a35..8e69bafce2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/DefaultStreamImplementationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/DefaultStreamImplementationTest.java @@ -5,10 +5,7 @@ */ package org.mapstruct.ap.test.java8stream.defaultimplementation; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -18,11 +15,11 @@ import java.util.TreeSet; import java.util.stream.Stream; -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; @WithClasses({ Source.class, @@ -32,10 +29,9 @@ SourceTargetMapper.class }) @IssueKey("962") -@RunWith(AnnotationProcessorTestRunner.class) public class DefaultStreamImplementationTest { - @Test + @ProcessorTest public void shouldUseDefaultImplementationForNavigableSet() { NavigableSet target = SourceTargetMapper.INSTANCE.streamToNavigableSet( createSourceFooStream() ); @@ -44,7 +40,7 @@ public void shouldUseDefaultImplementationForNavigableSet() { assertThat( target ).isInstanceOf( TreeSet.class ); } - @Test + @ProcessorTest public void shouldUseDefaultImplementationForCollection() { Collection target = SourceTargetMapper.INSTANCE.streamToCollection( createSourceFooStream() ); @@ -53,7 +49,7 @@ public void shouldUseDefaultImplementationForCollection() { assertThat( target ).isInstanceOf( ArrayList.class ); } - @Test + @ProcessorTest public void shouldUseDefaultImplementationForIterable() { Iterable target = SourceTargetMapper.INSTANCE.streamToIterable( createSourceFooStream() ); @@ -62,7 +58,7 @@ public void shouldUseDefaultImplementationForIterable() { assertThat( target ).isInstanceOf( ArrayList.class ); } - @Test + @ProcessorTest public void shouldUseDefaultImplementationForList() { List target = SourceTargetMapper.INSTANCE.streamToList( createSourceFooStream() ); @@ -70,7 +66,7 @@ public void shouldUseDefaultImplementationForList() { assertThat( target ).isInstanceOf( ArrayList.class ); } - @Test + @ProcessorTest public void shouldUseDefaultImplementationForSet() { Set target = SourceTargetMapper.INSTANCE.streamToSet( createSourceFooStream() ); @@ -79,7 +75,7 @@ public void shouldUseDefaultImplementationForSet() { assertThat( target ).isInstanceOf( HashSet.class ); } - @Test + @ProcessorTest public void shouldUseDefaultImplementationForSortedSet() { SortedSet target = SourceTargetMapper.INSTANCE.streamToSortedSet( createSourceFooStream() ); @@ -88,9 +84,9 @@ public void shouldUseDefaultImplementationForSortedSet() { assertThat( target ).isInstanceOf( TreeSet.class ); } - @Test + @ProcessorTest public void shouldUseTargetParameterForMapping() { - List target = new ArrayList(); + List target = new ArrayList<>(); SourceTargetMapper.INSTANCE.sourceFoosToTargetFoosUsingTargetParameter( target, createSourceFooStream() @@ -99,7 +95,7 @@ public void shouldUseTargetParameterForMapping() { assertResultList( target ); } - @Test + @ProcessorTest public void shouldUseTargetParameterForArrayMapping() { TargetFoo[] target = new TargetFoo[3]; SourceTargetMapper.INSTANCE.streamToArrayUsingTargetParameter( @@ -111,7 +107,7 @@ public void shouldUseTargetParameterForArrayMapping() { assertThat( target ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ), null ); } - @Test + @ProcessorTest public void shouldUseTargetParameterForArrayMappingAndSmallerArray() { TargetFoo[] target = new TargetFoo[1]; SourceTargetMapper.INSTANCE.streamToArrayUsingTargetParameter( @@ -123,7 +119,7 @@ public void shouldUseTargetParameterForArrayMappingAndSmallerArray() { assertThat( target ).containsOnly( new TargetFoo( "Bob" ) ); } - @Test + @ProcessorTest public void shouldUseAndReturnTargetParameterForArrayMapping() { TargetFoo[] target = new TargetFoo[3]; TargetFoo[] result = @@ -134,7 +130,7 @@ public void shouldUseAndReturnTargetParameterForArrayMapping() { assertThat( target ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ), null ); } - @Test + @ProcessorTest public void shouldUseAndReturnTargetParameterForArrayMappingAndSmallerArray() { TargetFoo[] target = new TargetFoo[1]; TargetFoo[] result = @@ -145,9 +141,22 @@ public void shouldUseAndReturnTargetParameterForArrayMappingAndSmallerArray() { assertThat( target ).containsOnly( new TargetFoo( "Bob" ) ); } - @Test + @ProcessorTest + @IssueKey("1752") + public void shouldUseAndReturnTargetParameterArrayForNullSource() { + TargetFoo[] target = new TargetFoo[1]; + target[0] = new TargetFoo( "Bob" ); + TargetFoo[] result = + SourceTargetMapper.INSTANCE.streamToArrayUsingTargetParameterAndReturn( null, target ); + + assertThat( result ).isSameAs( target ); + assertThat( target ).isNotNull(); + assertThat( target ).containsOnly( new TargetFoo( "Bob" ) ); + } + + @ProcessorTest public void shouldUseAndReturnTargetParameterForMapping() { - List target = new ArrayList(); + List target = new ArrayList<>(); Iterable result = SourceTargetMapper.INSTANCE .sourceFoosToTargetFoosUsingTargetParameterAndReturn( createSourceFooStream(), target ); @@ -156,7 +165,21 @@ public void shouldUseAndReturnTargetParameterForMapping() { assertResultList( target ); } - @Test + @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 ); + } + + @ProcessorTest public void shouldUseDefaultImplementationForListWithoutSetter() { Source source = new Source(); source.setFooStream( createSourceFooStream() ); @@ -172,6 +195,6 @@ private void assertResultList(Iterable fooIterable) { } private Stream createSourceFooStream() { - return Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ).stream(); + return Stream.of( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/NoSetterStreamMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/NoSetterStreamMappingTest.java index 03617607dd..f82a522bf5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/NoSetterStreamMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/NoSetterStreamMappingTest.java @@ -5,16 +5,14 @@ */ package org.mapstruct.ap.test.java8stream.defaultimplementation; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; -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; /** * @author Filip Hrisfaov @@ -22,13 +20,12 @@ */ @WithClasses({ NoSetterMapper.class, NoSetterSource.class, NoSetterTarget.class }) @IssueKey("962") -@RunWith(AnnotationProcessorTestRunner.class) public class NoSetterStreamMappingTest { - @Test + @ProcessorTest public void compilesAndMapsCorrectly() { NoSetterSource source = new NoSetterSource(); - source.setListValues( Arrays.asList( "foo", "bar" ).stream() ); + source.setListValues( Stream.of( "foo", "bar" ) ); NoSetterTarget target = NoSetterMapper.INSTANCE.toTarget( source ); @@ -37,7 +34,7 @@ public void compilesAndMapsCorrectly() { // now test existing instances NoSetterSource source2 = new NoSetterSource(); - source2.setListValues( Arrays.asList( "baz" ).stream() ); + source2.setListValues( Stream.of( "baz" ) ); List originalCollectionInstance = target.getListValues(); NoSetterTarget target2 = NoSetterMapper.INSTANCE.toTargetWithExistingTarget( source2, target ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/NoSetterTarget.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/NoSetterTarget.java index 39c7c17cbc..7992012c81 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/NoSetterTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/NoSetterTarget.java @@ -13,7 +13,7 @@ * */ public class NoSetterTarget { - private List listValues = new ArrayList(); + private List listValues = new ArrayList<>(); public List getListValues() { return listValues; diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/SourceTargetMapper.java index 77d24916f5..b027dc9a0e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/SourceTargetMapper.java @@ -22,7 +22,7 @@ public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - @Mapping(source = "fooStream", target = "fooListNoSetter") + @Mapping(target = "fooListNoSetter", source = "fooStream") Target sourceToTarget(Source source); TargetFoo sourceFooToTargetFoo(SourceFoo sourceFoo); diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/Target.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/Target.java index 5168c6b043..2518414731 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/Target.java @@ -14,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/java8stream/defaultimplementation/TargetFoo.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/TargetFoo.java index 0a4061e712..10439fe2b8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/TargetFoo.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/defaultimplementation/TargetFoo.java @@ -45,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/java8stream/erroneous/ErroneousStreamMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/erroneous/ErroneousStreamMappingTest.java index dfa9849f0f..f3e540b7ca 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/erroneous/ErroneousStreamMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/erroneous/ErroneousStreamMappingTest.java @@ -7,16 +7,14 @@ 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/stream types, iterable and non-iterable types etc. @@ -31,10 +29,9 @@ * @author Filip Hrisafov */ @IssueKey("962") -@RunWith(AnnotationProcessorTestRunner.class) public class ErroneousStreamMappingTest { - @Test + @ProcessorTest @WithClasses({ ErroneousStreamToNonStreamMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -42,17 +39,17 @@ public class ErroneousStreamMappingTest { @Diagnostic(type = ErroneousStreamToNonStreamMapper.class, kind = Kind.ERROR, line = 15, - messageRegExp = "Can't generate mapping method from iterable type to non-iterable type"), + message = "Can't generate mapping method from iterable type from java stdlib to non-iterable type."), @Diagnostic(type = ErroneousStreamToNonStreamMapper.class, kind = Kind.ERROR, line = 17, - messageRegExp = "Can't generate mapping method from non-iterable type to iterable type") + message = "Can't generate mapping method from non-iterable type to iterable type from java stdlib.") } ) public void shouldFailToGenerateImplementationBetweenStreamAndNonStreamOrIterable() { } - @Test + @ProcessorTest @WithClasses({ ErroneousStreamToPrimitivePropertyMapper.class, Source.class, Target.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -60,18 +57,14 @@ public void shouldFailToGenerateImplementationBetweenStreamAndNonStreamOrIterabl @Diagnostic(type = ErroneousStreamToPrimitivePropertyMapper.class, kind = Kind.ERROR, line = 13, - messageRegExp = - "Can't map property \"java.util.stream.Stream strings\" to \"int strings\". " - + - "Consider to declare/implement a mapping method: \"int map\\(java.util.stream.Stream" - + " value\\)\"") + message = "Can't map property \"Stream strings\" to \"int strings\". " + + "Consider to declare/implement a mapping method: \"int map(Stream value)\".") } ) public void shouldFailToGenerateImplementationBetweenCollectionAndPrimitive() { } - @Test + @ProcessorTest @WithClasses({ EmptyStreamMappingMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -79,24 +72,24 @@ public void shouldFailToGenerateImplementationBetweenCollectionAndPrimitive() { @Diagnostic(type = EmptyStreamMappingMapper.class, kind = Kind.ERROR, line = 23, - messageRegExp = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + message = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + "undefined in @IterableMapping, define at least one of them."), @Diagnostic(type = EmptyStreamMappingMapper.class, kind = Kind.ERROR, line = 26, - messageRegExp = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + message = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + "undefined in @IterableMapping, define at least one of them."), @Diagnostic(type = EmptyStreamMappingMapper.class, kind = Kind.ERROR, line = 29, - messageRegExp = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + message = "'nullValueMappingStrategy','dateformat', 'qualifiedBy' and 'elementTargetType' are " + "undefined in @IterableMapping, define at least one of them.") } ) public void shouldFailOnEmptyIterableAnnotationStreamMappings() { } - @Test + @ProcessorTest @WithClasses({ ErroneousStreamToStreamNoElementMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -104,16 +97,15 @@ public void shouldFailOnEmptyIterableAnnotationStreamMappings() { @Diagnostic(type = ErroneousStreamToStreamNoElementMappingFound.class, kind = Kind.ERROR, line = 24, - messageRegExp = "No target bean properties found: can't map Stream element \".*WithProperties " - + "withProperties\" to \".*NoProperties noProperties\". " - + "Consider to declare/implement a mapping method: \".*NoProperties " - + "map\\(.*WithProperties value\\)" ) + message = "No target bean properties found: " + + "can't map Stream element \"WithProperties withProperties\" to \"NoProperties noProperties\". " + + "Consider to declare/implement a mapping method: \"NoProperties map(WithProperties value)\".") } ) public void shouldFailOnNoElementMappingFoundForStreamToStream() { } - @Test + @ProcessorTest @IssueKey("993") @WithClasses({ ErroneousStreamToStreamNoElementMappingFoundDisabledAuto.class }) @ExpectedCompilationOutcome( @@ -122,14 +114,14 @@ public void shouldFailOnNoElementMappingFoundForStreamToStream() { @Diagnostic(type = ErroneousStreamToStreamNoElementMappingFoundDisabledAuto.class, kind = Kind.ERROR, line = 19, - messageRegExp = "Can't map stream element \".*AttributedString\" to \".*String \". Consider to " + - "declare/implement a mapping method: \".*String map(.*AttributedString value)") + message = "Can't map stream element \"AttributedString\" to \"String \". " + + "Consider to declare/implement a mapping method: \"String map(AttributedString value)\".") } ) public void shouldFailOnNoElementMappingFoundForStreamToStreamWithDisabledAuto() { } - @Test + @ProcessorTest @WithClasses({ ErroneousListToStreamNoElementMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -137,17 +129,15 @@ public void shouldFailOnNoElementMappingFoundForStreamToStreamWithDisabledAuto() @Diagnostic(type = ErroneousListToStreamNoElementMappingFound.class, kind = Kind.ERROR, line = 25, - messageRegExp = "No target bean properties found: can't map Stream element \".*WithProperties " - + "withProperties\" to \".*NoProperties noProperties\"." - + " Consider to declare/implement a mapping method: \".*NoProperties map\\(" - + ".*WithProperties " - + "value\\)" ) + message = "No target bean properties found: " + + "can't map Stream element \"WithProperties withProperties\" to \"NoProperties noProperties\". " + + "Consider to declare/implement a mapping method: \"NoProperties map(WithProperties value)\".") } ) public void shouldFailOnNoElementMappingFoundForListToStream() { } - @Test + @ProcessorTest @IssueKey("993") @WithClasses({ ErroneousListToStreamNoElementMappingFoundDisabledAuto.class }) @ExpectedCompilationOutcome( @@ -156,15 +146,14 @@ public void shouldFailOnNoElementMappingFoundForListToStream() { @Diagnostic(type = ErroneousListToStreamNoElementMappingFoundDisabledAuto.class, kind = Kind.ERROR, line = 20, - messageRegExp = "Can't map stream element \".*AttributedString\" to " - + "\".*String \". Consider to declare/implement a mapping method: \".*String " - + "map\\(.*AttributedString value\\)" ) + message = "Can't map stream element \"AttributedString\" to \"String \". " + + "Consider to declare/implement a mapping method: \"String map(AttributedString value)\".") } ) public void shouldFailOnNoElementMappingFoundForListToStreamWithDisabledAuto() { } - @Test + @ProcessorTest @WithClasses({ ErroneousStreamToListNoElementMappingFound.class, NoProperties.class, WithProperties.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -172,16 +161,15 @@ public void shouldFailOnNoElementMappingFoundForListToStreamWithDisabledAuto() { @Diagnostic(type = ErroneousStreamToListNoElementMappingFound.class, kind = Kind.ERROR, line = 25, - messageRegExp = "No target bean properties found: can't map Stream element \".*WithProperties " - + "withProperties\" to .*NoProperties noProperties\"." - + " Consider to declare/implement a mapping method: \".*NoProperties map(" - + ".*WithProperties value)" ) + message = "No target bean properties found: " + + "can't map Stream element \"WithProperties withProperties\" to \"NoProperties noProperties\". " + + "Consider to declare/implement a mapping method: \"NoProperties map(WithProperties value)\".") } ) public void shouldFailOnNoElementMappingFoundForStreamToList() { } - @Test + @ProcessorTest @IssueKey("993") @WithClasses({ ErroneousStreamToListNoElementMappingFoundDisabledAuto.class }) @ExpectedCompilationOutcome( @@ -190,8 +178,8 @@ public void shouldFailOnNoElementMappingFoundForStreamToList() { @Diagnostic(type = ErroneousStreamToListNoElementMappingFoundDisabledAuto.class, kind = Kind.ERROR, line = 20, - messageRegExp = "Can't map stream element \".*AttributedString\" to .*String \". Consider to " + - "declare/implement a mapping method: \".*String map(.*AttributedString value)") + message = "Can't map stream element \"AttributedString\" to \"String \". " + + "Consider to declare/implement a mapping method: \"String map(AttributedString value)\".") } ) public void shouldFailOnNoElementMappingFoundForStreamToListWithDisabledAuto() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/forged/ForgedStreamMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/forged/ForgedStreamMappingTest.java index 67091f5798..2ad882891f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/forged/ForgedStreamMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/forged/ForgedStreamMappingTest.java @@ -5,35 +5,32 @@ */ package org.mapstruct.ap.test.java8stream.forged; -import static org.assertj.core.api.Assertions.assertThat; - import javax.tools.Diagnostic.Kind; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; 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 org.mapstruct.ap.testutil.runner.GeneratedSource; +import static org.assertj.core.api.Assertions.assertThat; + /** * Test for mappings between collection and stream types, * * @author Filip Hrisafov */ @IssueKey("962") -@RunWith(AnnotationProcessorTestRunner.class) public class ForgedStreamMappingTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest @WithClasses({ StreamMapper.class, Source.class, Target.class }) public void shouldForgeNewIterableMappingMethod() { @@ -56,7 +53,7 @@ public void shouldForgeNewIterableMappingMethod() { .doesNotContain( "Stream.empty()" ); } - @Test + @ProcessorTest @WithClasses({ ErroneousStreamNonMappableStreamMapper.class, ErroneousNonMappableStreamSource.class, @@ -70,13 +67,15 @@ public void shouldForgeNewIterableMappingMethod() { @Diagnostic(type = ErroneousStreamNonMappableStreamMapper.class, kind = Kind.ERROR, line = 17, - messageRegExp = "No target bean properties found: can't map Stream element \".* nonMappableStream\" " - + "to \".* nonMappableStream\". Consider to declare/implement a mapping method: .*." ) } + message = "No target bean properties found: " + + "can't map Stream element \"Foo nonMappableStream\" to \"Bar nonMappableStream\". " + + "Consider to declare/implement a mapping method: \"Bar map(Foo value)\".") + } ) public void shouldGenerateNonMappableMethodForSetMapping() { } - @Test + @ProcessorTest @WithClasses({ StreamMapper.class, Source.class, Target.class }) public void shouldForgeNewIterableMappingMethodReturnNullOnNullSource() { @@ -94,7 +93,7 @@ public void shouldForgeNewIterableMappingMethodReturnNullOnNullSource() { assertThat( source2.getFooStream3() ).isNull(); } - @Test + @ProcessorTest @WithClasses({ StreamMapperNullValueMappingReturnDefault.class, Source.class, Target.class }) public void shouldForgeNewIterableMappingMethodReturnEmptyOnNullSource() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/streamtononiterable/StreamToNonIterableMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/streamtononiterable/StreamToNonIterableMappingTest.java index 2e93d1a378..26c8e88324 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/streamtononiterable/StreamToNonIterableMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/streamtononiterable/StreamToNonIterableMappingTest.java @@ -5,32 +5,29 @@ */ package org.mapstruct.ap.test.java8stream.streamtononiterable; -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.Arrays; +import java.util.stream.Stream; -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; @WithClasses({ Source.class, Target.class, SourceTargetMapper.class, StringListMapper.class }) @IssueKey("962") -@RunWith(AnnotationProcessorTestRunner.class) public class StreamToNonIterableMappingTest { - @Test + @ProcessorTest public void shouldMapStringStreamToStringUsingCustomMapper() { Source source = new Source(); - source.setNames( Arrays.asList( "Alice", "Bob", "Jim" ).stream() ); + source.setNames( Stream.of( "Alice", "Bob", "Jim" ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); assertThat( target.getNames() ).isEqualTo( "Alice-Bob-Jim" ); } - @Test + @ProcessorTest public void shouldReverseMapStringStreamToStringUsingCustomMapper() { Target target = new Target(); target.setNames( "Alice-Bob-Jim" ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/streamtononiterable/StringListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/streamtononiterable/StringListMapper.java index d1b6e959f8..f8da66374e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/streamtononiterable/StringListMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/streamtononiterable/StringListMapper.java @@ -16,6 +16,6 @@ public String stringListToString(Stream strings) { } public Stream stringToStringList(String string) { - return string == null ? null : Arrays.asList( string.split( "-" ) ).stream(); + return string == null ? null : Arrays.stream( string.split( "-" ) ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java index bb8434fd63..063a5e5615 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Idea.java @@ -11,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/java8stream/wildcard/Plan.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java index 8c55ebc273..7aa366ec95 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/Plan.java @@ -11,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/java8stream/wildcard/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/WildCardTest.java index a24a9d8b5d..a039951495 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/WildCardTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/java8stream/wildcard/WildCardTest.java @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.java8stream.wildcard; -import static org.assertj.core.api.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.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; /** * @author Filip Hrisafov */ @IssueKey("962") -@RunWith(AnnotationProcessorTestRunner.class) public class WildCardTest { - @Test + @ProcessorTest @WithClasses({ ExtendsBoundSourceTargetMapper.class, ExtendsBoundSource.class, @@ -43,7 +40,7 @@ public void shouldGenerateExtendsBoundSourceForgedStreamMethod() { } - @Test + @ProcessorTest @WithClasses({ SourceSuperBoundTargetMapper.class, Source.class, @@ -63,7 +60,7 @@ public void shouldGenerateSuperBoundTargetForgedIterableMethod() { } - @Test + @ProcessorTest @WithClasses({ ErroneousIterableSuperBoundSourceMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -71,13 +68,13 @@ public void shouldGenerateSuperBoundTargetForgedIterableMethod() { @Diagnostic(type = ErroneousIterableSuperBoundSourceMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 21, - messageRegExp = "Can't generate mapping method for a wildcard super bound source.") + message = "Can't generate mapping method for a wildcard super bound source.") } ) public void shouldFailOnSuperBoundSource() { } - @Test + @ProcessorTest @WithClasses({ ErroneousIterableExtendsBoundTargetMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -85,13 +82,13 @@ public void shouldFailOnSuperBoundSource() { @Diagnostic(type = ErroneousIterableExtendsBoundTargetMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 21, - messageRegExp = "Can't generate mapping method for a wildcard extends bound result.") + message = "Can't generate mapping method for a wildcard extends bound result.") } ) public void shouldFailOnExtendsBoundTarget() { } - @Test + @ProcessorTest @WithClasses({ ErroneousIterableTypeVarBoundMapperOnMethod.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -99,13 +96,13 @@ public void shouldFailOnExtendsBoundTarget() { @Diagnostic(type = ErroneousIterableTypeVarBoundMapperOnMethod.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 21, - messageRegExp = "Can't generate mapping method for a generic type variable target.") + message = "Can't generate mapping method for a generic type variable target.") } ) public void shouldFailOnTypeVarSource() { } - @Test + @ProcessorTest @WithClasses({ ErroneousIterableTypeVarBoundMapperOnMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -113,7 +110,7 @@ public void shouldFailOnTypeVarSource() { @Diagnostic(type = ErroneousIterableTypeVarBoundMapperOnMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 21, - messageRegExp = "Can't generate mapping method for a generic type variable source.") + message = "Can't generate mapping method for a generic type variable source.") } ) public void shouldFailOnTypeVarTarget() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.java new file mode 100644 index 0000000000..39f6da2890 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/ErroneousJavadocMapper.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.javadoc; + +import org.mapstruct.Javadoc; +import org.mapstruct.Mapper; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@Mapper +@Javadoc +public interface ErroneousJavadocMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.java new file mode 100644 index 0000000000..eb6285ff13 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithAttributesMapper.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.javadoc; + +import org.mapstruct.Javadoc; +import org.mapstruct.Mapper; + +@Mapper +@Javadoc( + value = "This is the description", + authors = { "author1", "author2" }, + deprecated = "Use {@link OtherMapper} instead", + since = "0.1" +) +@Deprecated +public interface JavadocAnnotatedWithAttributesMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.java new file mode 100644 index 0000000000..150d7e5f76 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocAnnotatedWithValueMapper.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.javadoc; + +import org.mapstruct.Javadoc; +import org.mapstruct.Mapper; + +@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") +@Deprecated +public interface JavadocAnnotatedWithValueMapper { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.java new file mode 100644 index 0000000000..316114389f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/javadoc/JavadocTest.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.javadoc; + +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; + +/** + * @author Jose Carlos Campanero Ortiz + */ +@IssueKey("2987") +class JavadocTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses( { JavadocAnnotatedWithValueMapper.class } ) + void javadocAnnotatedWithValueMapper() { + generatedSource.addComparisonToFixtureFor( JavadocAnnotatedWithValueMapper.class ); + } + + @ProcessorTest + @WithClasses( { JavadocAnnotatedWithAttributesMapper.class } ) + void javadocAnnotatedWithAttributesMapper() { + generatedSource.addComparisonToFixtureFor( JavadocAnnotatedWithAttributesMapper.class ); + } + + @ProcessorTest + @IssueKey("2987") + @WithClasses({ ErroneousJavadocMapper.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousJavadocMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 15, + message = "'value', 'authors', 'deprecated' and 'since' are undefined in @Javadoc, " + + "define at least one of them.") + } + ) + void shouldFailOnEmptyJavadocAnnotation() { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt new file mode 100644 index 0000000000..e452102f6e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsProperty.kt @@ -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.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class AllDefaultsProperty(val firstName: String? = null, val lastName: String? = null) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.java new file mode 100644 index 0000000000..1f8aab4624 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/AllDefaultsPropertyMapper.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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface AllDefaultsPropertyMapper { + + AllDefaultsPropertyMapper INSTANCE = Mappers.getMapper( AllDefaultsPropertyMapper.class ); + + AllDefaultsProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + + public Source(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt new file mode 100644 index 0000000000..3fbd48b0b1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerDto.kt @@ -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.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class CustomerDto(var name: String?, var email: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerEntity.java new file mode 100644 index 0000000000..7ddd41bda0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/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.ap.test.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/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/CustomerMapper.java new file mode 100644 index 0000000000..1830a9c583 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/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.ap.test.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 fromData(CustomerDto record); + + @InheritInverseConfiguration + CustomerDto toData(CustomerEntity entity); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.kt new file mode 100644 index 0000000000..58ebbd128d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/Default.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.ap.test.kotlin.data + +@Target(AnnotationTarget.CONSTRUCTOR) +@Retention(AnnotationRetention.BINARY) +annotation class Default diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt new file mode 100644 index 0000000000..f5ff27d3d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultProperty.kt @@ -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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class DefaultPropertySource(val default: Boolean, val identifier: String?) + +class DefaultPropertyTarget( + var default: Boolean, + var identifier: String? +) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.java new file mode 100644 index 0000000000..ce08e26e7e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/DefaultPropertyMapper.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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface DefaultPropertyMapper { + + DefaultPropertyMapper INSTANCE = Mappers.getMapper( DefaultPropertyMapper.class ); + + DefaultPropertyTarget map(DefaultPropertySource source); + + DefaultPropertySource map(DefaultPropertyTarget target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java new file mode 100644 index 0000000000..94d0d90d75 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/KotlinDataTest.java @@ -0,0 +1,521 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.kotlin.data; + +import org.junit.jupiter.api.Nested; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; +import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.WithTestDependency; +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 + */ +class KotlinDataTest { + + @Nested + @WithClasses({ + CustomerEntity.class, + CustomerMapper.class + }) + @WithKotlinSources("CustomerDto.kt") + class Standard { + + @ProcessorTest + void shouldMapData() { + CustomerEntity customer = CustomerMapper.INSTANCE.fromData( new CustomerDto( + "Kermit", + "kermit@test.com" + ) ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getMail() ).isEqualTo( "kermit@test.com" ); + } + + @ProcessorTest + void shouldMapIntoData() { + CustomerEntity entity = new CustomerEntity(); + entity.setName( "Kermit" ); + entity.setMail( "kermit@test.com" ); + + CustomerDto customer = CustomerMapper.INSTANCE.toData( entity ); + + assertThat( customer ).isNotNull(); + assertThat( customer.getName() ).isEqualTo( "Kermit" ); + assertThat( customer.getEmail() ).isEqualTo( "kermit@test.com" ); + } + + } + + @Nested + @WithClasses({ + SinglePropertyMapper.class, + }) + @WithKotlinSources("SingleProperty.kt") + class SingleData { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + SingleProperty property = SinglePropertyMapper.INSTANCE.map( new SinglePropertyMapper.Source( "test" ) ); + assertThat( property ).isNotNull(); + assertThat( property.getValue() ).isEqualTo( "test" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = SinglePropertyMapper.class, + line = 19, + message = "Unmapped target property: \"copy\"." + ) + } + ) + void shouldCompileWithWarnings() { + + SingleProperty property = SinglePropertyMapper.INSTANCE.map( new SinglePropertyMapper.Source( "test" ) ); + assertThat( property ).isNotNull(); + assertThat( property.getValue() ).isEqualTo( "test" ); + } + } + + @Nested + @WithClasses({ + AllDefaultsPropertyMapper.class, + }) + @WithKotlinSources("AllDefaultsProperty.kt") + class AllDefaults { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + AllDefaultsProperty property = AllDefaultsPropertyMapper.INSTANCE.map( new AllDefaultsPropertyMapper.Source( + "Kermit", + "the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = AllDefaultsPropertyMapper.class, + line = 19, + message = "No target property found for target \"AllDefaultsProperty\"." + ) + } + ) + void shouldCompileWithWarnings() { + + AllDefaultsProperty property = AllDefaultsPropertyMapper.INSTANCE.map( new AllDefaultsPropertyMapper.Source( + "Kermit", + "the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + } + } + + @Nested + @WithClasses({ + MultiConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiConstructorProperty.kt") + class MultiConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + MultiConstructorProperty property = MultiConstructorPropertyMapper.INSTANCE + .map( new MultiConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isEqualTo( "Kermit the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiConstructorPropertyMapper.class, + line = 19, + messageRegExp = "Ambiguous constructors found for creating .*MultiConstructorProperty: " + + "MultiConstructorProperty\\(java.lang.String, java.lang.String.*\\), " + + "MultiConstructorProperty\\(java.lang.String, java.lang.String.*\\)\\. " + + "Either declare parameterless constructor or annotate the default constructor with an " + + "annotation named @Default\\." + ) + } + ) + void shouldFailToCompile() { + + } + } + + @Nested + @WithClasses({ + MultiSimilarConstructorPropertyMapper.class, + }) + @WithKotlinSources("MultiSimilarConstructorProperty.kt") + class MultiSimilarConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + PrimaryString primaryString = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + "Kermit the Frog" + ); + assertThat( primaryString ).isNotNull(); + assertThat( primaryString.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryString.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryString.getDisplayName() ).isEqualTo( "Kermit the Frog" ); + + PrimaryInt primaryInt = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42 + ); + assertThat( primaryInt ).isNotNull(); + assertThat( primaryInt.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryInt.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryInt.getAge() ).isEqualTo( 42 ); + + PrimaryLong primaryLong = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42L + ); + assertThat( primaryLong ).isNotNull(); + assertThat( primaryLong.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryLong.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryLong.getAge() ).isEqualTo( 42 ); + + PrimaryBoolean primaryBoolean = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + true + ); + assertThat( primaryBoolean ).isNotNull(); + assertThat( primaryBoolean.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryBoolean.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryBoolean.getActive() ).isTrue(); + + PrimaryByte primaryByte = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + (byte) 4 + ); + assertThat( primaryByte ).isNotNull(); + assertThat( primaryByte.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryByte.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryByte.getB() ).isEqualTo( (byte) 4 ); + + PrimaryShort primaryShort = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + (short) 4 + ); + assertThat( primaryShort ).isNotNull(); + assertThat( primaryShort.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryShort.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryShort.getAge() ).isEqualTo( (short) 4 ); + + PrimaryChar primaryChar = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 't' + ); + assertThat( primaryChar ).isNotNull(); + assertThat( primaryChar.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryChar.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryChar.getC() ).isEqualTo( 't' ); + + PrimaryFloat primaryFloat = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42.2f + ); + assertThat( primaryFloat ).isNotNull(); + assertThat( primaryFloat.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryFloat.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryFloat.getPrice() ).isEqualTo( 42.2f ); + + PrimaryDouble primaryDouble = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + 42.2 + ); + assertThat( primaryDouble ).isNotNull(); + assertThat( primaryDouble.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryDouble.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryDouble.getPrice() ).isEqualTo( 42.2d ); + + PrimaryArray primaryArray = MultiSimilarConstructorPropertyMapper.INSTANCE.map( + new MultiSimilarConstructorPropertyMapper.Source( + "Kermit", + "the Frog" + ), + new String[] { "Kermit", "the Frog" } + ); + assertThat( primaryArray ).isNotNull(); + assertThat( primaryArray.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( primaryArray.getLastName() ).isEqualTo( "the Frog" ); + assertThat( primaryArray.getElements() ).containsExactly( "Kermit", "the Frog" ); + } + + @ProcessorTest + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 19, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryString" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 21, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryInt" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 23, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryLong" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 25, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryBoolean" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 27, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryByte" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 29, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryShort" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 31, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryChar" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 33, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryFloat" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 35, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryDouble" + ), + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = MultiSimilarConstructorPropertyMapper.class, + line = 37, + messageRegExp = "Ambiguous constructors found for creating .*PrimaryArray" + ) + + + } + ) + void shouldFailToCompile() { + + } + } + + @Nested + @WithClasses({ + MultiDefaultConstructorPropertyMapper.class, + }) + @WithKotlinSources({ + "MultiDefaultConstructorProperty.kt", + "Default.kt" + }) + class MultiDefaultConstructor { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + MultiDefaultConstructorProperty property = MultiDefaultConstructorPropertyMapper.INSTANCE + .map( new MultiDefaultConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isNull(); + } + + @ProcessorTest + void shouldCompileWithoutKotlin() { + MultiDefaultConstructorProperty property = MultiDefaultConstructorPropertyMapper.INSTANCE + .map( new MultiDefaultConstructorPropertyMapper.Source( + "Kermit", + "the Frog", + "Kermit the Frog" + ) ); + assertThat( property ).isNotNull(); + assertThat( property.getFirstName() ).isEqualTo( "Kermit" ); + assertThat( property.getLastName() ).isEqualTo( "the Frog" ); + assertThat( property.getDisplayName() ).isNull(); + } + } + + @Nested + @WithClasses({ + UnsignedPropertyMapper.class, + }) + @WithKotlinSources("UnsignedProperty.kt") + class Unsigned { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + UnsignedProperty property = UnsignedPropertyMapper.INSTANCE.map( new UnsignedPropertyMapper.Source( 10 ) ); + assertThat( property ).isNotNull(); + assertThat( property.getAge() ).isEqualTo( 10 ); + + UnsignedPropertyMapper.Source source = UnsignedPropertyMapper.INSTANCE.map( new UnsignedProperty( 20 ) ); + assertThat( source ).isNotNull(); + assertThat( source.getAge() ).isEqualTo( 20 ); + } + + @ProcessorTest + @WithTestDependency( "kotlin-stdlib" ) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.WARNING, + type = UnsignedPropertyMapper.class, + line = 19, + messageRegExp = "Unmapped target property: \"copy-.*\"\\." + ) + } + ) + void shouldCompileWithoutKotlinJvmMetadata() { + UnsignedProperty property = UnsignedPropertyMapper.INSTANCE.map( new UnsignedPropertyMapper.Source( 10 ) ); + assertThat( property ).isNotNull(); + assertThat( property.getAge() ).isEqualTo( 10 ); + + UnsignedPropertyMapper.Source source = UnsignedPropertyMapper.INSTANCE.map( new UnsignedProperty( 20 ) ); + assertThat( source ).isNotNull(); + assertThat( source.getAge() ).isEqualTo( 20 ); + } + } + + @Nested + @WithClasses({ + DefaultPropertyMapper.class, + }) + @WithKotlinSources("DefaultProperty.kt") + class Default { + + @ProcessorTest + @WithKotlin + void shouldCompileWithoutWarnings() { + + DefaultPropertyTarget target = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertySource( + true, + "test" + ) ); + assertThat( target ).isNotNull(); + assertThat( target.getDefault() ).isTrue(); + assertThat( target.getIdentifier() ).isEqualTo( "test" ); + + DefaultPropertySource source = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertyTarget( + false, + "private" + ) ); + assertThat( source ).isNotNull(); + assertThat( source.getDefault() ).isFalse(); + assertThat( source.getIdentifier() ).isEqualTo( "private" ); + + } + + @ProcessorTest + void shouldCompileWithoutKotlin() { + DefaultPropertyTarget target = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertySource( + true, + "test" + ) ); + assertThat( target ).isNotNull(); + assertThat( target.getDefault() ).isTrue(); + assertThat( target.getIdentifier() ).isEqualTo( "test" ); + + DefaultPropertySource source = DefaultPropertyMapper.INSTANCE.map( new DefaultPropertyTarget( + false, + "private" + ) ); + assertThat( source ).isNotNull(); + assertThat( source.getDefault() ).isFalse(); + assertThat( source.getIdentifier() ).isEqualTo( "private" ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt new file mode 100644 index 0000000000..46cb8034e9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorProperty.kt @@ -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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class MultiConstructorProperty(var firstName: String?, var lastName: String?, var displayName: String?) { + constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.java new file mode 100644 index 0000000000..3186226a57 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiConstructorPropertyMapper.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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiConstructorPropertyMapper { + + MultiConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiConstructorPropertyMapper.class ); + + MultiConstructorProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + private final String displayName; + + public Source(String firstName, String lastName, String displayName) { + this.firstName = firstName; + this.lastName = lastName; + this.displayName = displayName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt new file mode 100644 index 0000000000..9f9af16053 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorProperty.kt @@ -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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class MultiDefaultConstructorProperty(val firstName: String?, val lastName: String?, val displayName: String?) { + @Default + constructor(firstName: String?, lastName: String?) : this(firstName, lastName, null) +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.java new file mode 100644 index 0000000000..da04c01fb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiDefaultConstructorPropertyMapper.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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiDefaultConstructorPropertyMapper { + + MultiDefaultConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiDefaultConstructorPropertyMapper.class ); + + MultiDefaultConstructorProperty map(Source source); + + class Source { + + private final String firstName; + private final String lastName; + private final String displayName; + + public Source(String firstName, String lastName, String displayName) { + this.firstName = firstName; + this.lastName = lastName; + this.displayName = displayName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getDisplayName() { + return displayName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt new file mode 100644 index 0000000000..e122169075 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorProperty.kt @@ -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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class PrimaryString(var firstName: String?, var lastName: String?, var displayName: String?) { + constructor(firstName: String?, lastName: String?, age: Int) : this(firstName, lastName, null as String?) +} + +data class PrimaryInt(var firstName: String?, var lastName: String?, var age: Int) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, -1) +} + +data class PrimaryLong(var firstName: String?, var lastName: String?, var age: Long) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, -1) +} + +data class PrimaryBoolean(var firstName: String?, var lastName: String?, var active: Boolean) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, false) +} + +data class PrimaryByte(var firstName: String?, var lastName: String?, var b: Byte) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0) +} + +data class PrimaryShort(var firstName: String?, var lastName: String?, var age: Short) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0) +} + +data class PrimaryChar(var firstName: String?, var lastName: String?, var c: Char) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 'a') +} + +data class PrimaryFloat(var firstName: String?, var lastName: String?, var price: Float) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0.0f) +} + +data class PrimaryDouble(var firstName: String?, var lastName: String?, var price: Double) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, 0.0) +} + +@Suppress("ArrayInDataClass") +data class PrimaryArray(var firstName: String?, var lastName: String?, var elements: Array) { + constructor(firstName: String?, lastName: String?, displayName: String?) : this(firstName, lastName, emptyArray()) +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.java new file mode 100644 index 0000000000..86021cd6d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/MultiSimilarConstructorPropertyMapper.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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MultiSimilarConstructorPropertyMapper { + + MultiSimilarConstructorPropertyMapper INSTANCE = Mappers.getMapper( MultiSimilarConstructorPropertyMapper.class ); + + PrimaryString map(Source source, String displayName); + + PrimaryInt map(Source source, int age); + + PrimaryLong map(Source source, long age); + + PrimaryBoolean map(Source source, boolean active); + + PrimaryByte map(Source source, byte b); + + PrimaryShort map(Source source, short age); + + PrimaryChar map(Source source, char c); + + PrimaryFloat map(Source source, float price); + + PrimaryDouble map(Source source, double price); + + PrimaryArray map(Source source, String[] elements); + + class Source { + + private final String firstName; + private final String lastName; + + public Source(String firstName, String lastName) { + this.firstName = firstName; + this.lastName = lastName; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt new file mode 100644 index 0000000000..1bb63c410d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SingleProperty.kt @@ -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.ap.test.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class SingleProperty(val value: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.java new file mode 100644 index 0000000000..d3d70f261c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/SinglePropertyMapper.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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SinglePropertyMapper { + + SinglePropertyMapper INSTANCE = Mappers.getMapper( SinglePropertyMapper.class ); + + SingleProperty map(Source source); + + 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/kotlin/data/UnsignedProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt new file mode 100644 index 0000000000..c52426b3af --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedProperty.kt @@ -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.kotlin.data + +/** + * @author Filip Hrisafov + */ +data class UnsignedProperty(val age: UInt?) { + // Java-friendly secondary constructor + constructor(age: Int?) : this(age?.toUInt()) + + @JvmName("getAge") + fun getAgeAsLong(): Long? = age?.toLong() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.java new file mode 100644 index 0000000000..4f055c7b41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/UnsignedPropertyMapper.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.kotlin.data; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface UnsignedPropertyMapper { + + UnsignedPropertyMapper INSTANCE = Mappers.getMapper( UnsignedPropertyMapper.class ); + + UnsignedProperty map(Source source); + + Source map(UnsignedProperty property); + + class Source { + + private final int age; + + public Source(int age) { + this.age = age; + } + + public int getAge() { + return age; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/KotlinDataJdk17Test.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/KotlinDataJdk17Test.java new file mode 100644 index 0000000000..bea5ca9bd4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/KotlinDataJdk17Test.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.kotlin.data.jdk17; + +import org.junit.jupiter.api.Nested; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; +import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.runner.Compiler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +class KotlinDataJdk17Test { + + @Nested + @WithKotlinSources(value = "RecordProperty.kt") + @WithClasses(RecordPropertyMapper.class) + class Record { + + @ProcessorTest(Compiler.JDK) + @WithKotlin + void withKotlin() { + RecordPropertyMapper.Source source = new RecordPropertyMapper.Source(); + source.setFirstName( "John" ); + source.setLastName( "Doe" ); + + RecordProperty property = RecordPropertyMapper.INSTANCE.map( source ); + assertThat( property ).isNotNull(); + assertThat( property.firstName() ).isEqualTo( "John" ); + assertThat( property.lastName() ).isEqualTo( "Doe" ); + + source = RecordPropertyMapper.INSTANCE.map( property ); + assertThat( source ).isNotNull(); + assertThat( source.getFirstName() ).isEqualTo( "John" ); + assertThat( source.getLastName() ).isEqualTo( "Doe" ); + } + + @ProcessorTest(Compiler.JDK) + void withoutKotlin() { + RecordPropertyMapper.Source source = new RecordPropertyMapper.Source(); + source.setFirstName( "John" ); + source.setLastName( "Doe" ); + + RecordProperty property = RecordPropertyMapper.INSTANCE.map( source ); + assertThat( property ).isNotNull(); + assertThat( property.firstName() ).isEqualTo( "John" ); + assertThat( property.lastName() ).isEqualTo( "Doe" ); + + source = RecordPropertyMapper.INSTANCE.map( property ); + assertThat( source ).isNotNull(); + assertThat( source.getFirstName() ).isEqualTo( "John" ); + assertThat( source.getLastName() ).isEqualTo( "Doe" ); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/RecordProperty.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/RecordProperty.kt new file mode 100644 index 0000000000..28e8c9cfad --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/RecordProperty.kt @@ -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.kotlin.data.jdk17 + +/** + * @author Filip Hrisafov + */ +@JvmRecord +data class RecordProperty(val firstName: String?, val lastName: String?) diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/RecordPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/RecordPropertyMapper.java new file mode 100644 index 0000000000..ecbc235aa5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/data/jdk17/RecordPropertyMapper.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.kotlin.data.jdk17; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface RecordPropertyMapper { + + RecordPropertyMapper INSTANCE = Mappers.getMapper( RecordPropertyMapper.class ); + + RecordProperty map(Source source); + + Source map(RecordProperty source); + + class Source { + + private String firstName; + private String lastName; + + 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; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt new file mode 100644 index 0000000000..5a9084a685 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Dtos.kt @@ -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.kotlin.sealed + +sealed class VehicleDto { + var name: String? = null + var maker: String? = null + + class BikeDto : VehicleDto() { + var numberOfGears: Int = 0 + } + + class CarDto : VehicleDto() { + var manual: Boolean = false + } + +} + +sealed class MotorDto : VehicleDto() { + var cc: Int = 0 + + class DavidsonDto : MotorDto() { + var numberOfExhausts: Int = 0 + } + + class HarleyDto : MotorDto() { + var engineDb: Int = 0 + } +} + +class VehicleCollectionDto { + var vehicles: MutableList = mutableListOf() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt new file mode 100644 index 0000000000..b380bac231 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/Entities.kt @@ -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.kotlin.sealed + +sealed class Vehicle { + var name: String? = null + var vehicleManufacturingCompany: String? = null + + class Bike : Vehicle() { + var numberOfGears: Int = 0 + } + + class Car : Vehicle() { + var manual: Boolean = false + } + +} + +sealed class Motor : Vehicle() { + var cc: Int = 0 + + class Davidson : Motor() { + var numberOfExhausts: Int = 0 + } + + class Harley : Motor() { + var engineDb: Int = 0 + } +} + +class VehicleCollection { + var vehicles: MutableList = mutableListOf() +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassMapper.java new file mode 100644 index 0000000000..bde7dc24cf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/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.ap.test.kotlin.sealed; + +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 = Vehicle.Car.class, target = VehicleDto.CarDto.class ) + @SubclassMapping( source = Vehicle.Bike.class, target = VehicleDto.BikeDto.class ) + @SubclassMapping( source = Motor.Harley.class, target = MotorDto.HarleyDto.class ) + @SubclassMapping( source = Motor.Davidson.class, target = MotorDto.DavidsonDto.class ) + @Mapping( source = "vehicleManufacturingCompany", target = "maker") + VehicleDto map(Vehicle vehicle); + + VehicleCollection mapInverse(VehicleCollectionDto vehicles); + + @InheritInverseConfiguration + Vehicle mapInverse(VehicleDto dto); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.java new file mode 100644 index 0000000000..f7162ec3a2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/kotlin/sealed/SealedSubclassTest.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.kotlin.sealed; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlin; +import org.mapstruct.ap.testutil.WithKotlinSources; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + SealedSubclassMapper.class, +}) +@WithKotlinSources({ + "Dtos.kt", + "Entities.kt" +}) +@WithKotlin +public class SealedSubclassTest { + + @ProcessorTest + public void mappingIsDoneUsingSubclassMapping() { + VehicleCollection vehicles = new VehicleCollection(); + vehicles.getVehicles().add( new Vehicle.Car() ); + vehicles.getVehicles().add( new Vehicle.Bike() ); + vehicles.getVehicles().add( new Motor.Harley() ); + vehicles.getVehicles().add( new Motor.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( + VehicleDto.CarDto.class, + VehicleDto.BikeDto.class, + MotorDto.HarleyDto.class, + MotorDto.DavidsonDto.class + ); + } + + @ProcessorTest + public void inverseMappingIsDoneUsingSubclassMapping() { + VehicleCollectionDto vehicles = new VehicleCollectionDto(); + vehicles.getVehicles().add( new VehicleDto.CarDto() ); + vehicles.getVehicles().add( new VehicleDto.BikeDto() ); + vehicles.getVehicles().add( new MotorDto.HarleyDto() ); + vehicles.getVehicles().add( new MotorDto.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( + Vehicle.Car.class, + Vehicle.Bike.class, + Motor.Harley.class, + Motor.Davidson.class + ); + } + + @ProcessorTest + public void subclassMappingInheritsInverseMapping() { + VehicleCollectionDto vehiclesDto = new VehicleCollectionDto(); + VehicleDto.CarDto carDto = new VehicleDto.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/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java index cd7e56a93e..1f7fe423f5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/ConfigTest.java @@ -5,16 +5,14 @@ */ package org.mapstruct.ap.test.mapperconfig; -import static org.assertj.core.api.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.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; /** * @@ -31,10 +29,9 @@ CustomMapperViaMapperConfig.class, SourceTargetMapper.class } ) -@RunWith(AnnotationProcessorTestRunner.class) public class ConfigTest { - @Test + @ProcessorTest @WithClasses( { Target.class, SourceTargetMapper.class } ) public void shouldUseCustomMapperViaMapperForFooToEntity() { @@ -43,7 +40,7 @@ public void shouldUseCustomMapperViaMapperForFooToEntity() { assertThat( target.getFoo().getCreatedBy() ).isEqualTo( CustomMapperViaMapper.class.getSimpleName() ); } - @Test + @ProcessorTest @WithClasses( { Target.class, SourceTargetMapper.class } ) public void shouldUseCustomMapperViaMapperConfigForFooToDto() { @@ -52,24 +49,24 @@ public void shouldUseCustomMapperViaMapperConfigForFooToDto() { assertThat( source.getFoo().getCreatedBy() ).isEqualTo( CustomMapperViaMapperConfig.class.getSimpleName() ); } - @Test + @ProcessorTest @WithClasses( { TargetNoFoo.class, SourceTargetMapperWarn.class } ) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, diagnostics = { @Diagnostic(type = SourceTargetMapperWarn.class, kind = javax.tools.Diagnostic.Kind.WARNING, line = 24, - messageRegExp = "Unmapped target property: \"noFoo\"") + message = "Unmapped target property: \"noFoo\".") }) public void shouldUseWARNViaMapper() { } - @Test + @ProcessorTest @WithClasses( { TargetNoFoo.class, SourceTargetMapperErroneous.class } ) @ExpectedCompilationOutcome(value = CompilationResult.FAILED, diagnostics = { @Diagnostic(type = SourceTargetMapperErroneous.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, - messageRegExp = "Unmapped target property: \"noFoo\"") + message = "Unmapped target property: \"noFoo\".") }) public void shouldUseERRORViaMapperConfig() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/BoxDto.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/BoxDto.java new file mode 100644 index 0000000000..1e984b792e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/BoxDto.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.mappingcomposition; + +public class BoxDto { + + private String groupName; + private String designation; + private ShelveDto shelve; + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getDesignation() { + return designation; + } + + public void setDesignation(String designation) { + this.designation = designation; + } + + public ShelveDto getShelve() { + return shelve; + } + + public void setShelve(ShelveDto shelve) { + this.shelve = shelve; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/BoxEntity.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/BoxEntity.java new file mode 100644 index 0000000000..d0baa7c9d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/BoxEntity.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.mappingcomposition; + +import java.util.Date; + +public class BoxEntity { + + private Date creationDate; + private Long id; + private String name; + private String label; + private ShelveEntity shelve; + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public ShelveEntity getShelve() { + return shelve; + } + + public void setShelve(ShelveEntity shelve) { + this.shelve = shelve; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/CompositionTest.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/CompositionTest.java new file mode 100644 index 0000000000..0a5a361313 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/CompositionTest.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.mappingcomposition; + +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 + */ +@IssueKey("807") +@WithClasses({ + BoxDto.class, + BoxEntity.class, + ShelveDto.class, + ShelveEntity.class, + StorageMapper.class, + ToEntity.class +}) +public class CompositionTest { + + @ProcessorTest + public void shouldCompose() { + + Date now = new Date(); + Date anHourAgo = new Date( now.getTime() - 3600 * 1000 ); + Date anHourFromNow = new Date( now.getTime() + 3600 * 1000 ); + + ShelveDto shelve = new ShelveDto(); + shelve.setGroupName( "blue things" ); + shelve.setMaxWeight( 10.0d ); + shelve.setPath( "row5" ); + shelve.setStandNumber( 3 ); + + BoxDto box = new BoxDto(); + box.setDesignation( "blue item" ); + box.setGroupName( "blue stuff" ); + box.setShelve( shelve ); + + BoxEntity boxEntity = StorageMapper.INSTANCE.map( box ); + + // box + assertThat( boxEntity ).isNotNull(); + assertThat( boxEntity.getCreationDate() ).isBetween( anHourAgo, anHourFromNow ); + assertThat( boxEntity.getId() ).isNull(); + assertThat( boxEntity.getName() ).isEqualTo( "blue stuff" ); + assertThat( boxEntity.getLabel() ).isEqualTo( "blue item" ); + + // shelve + ShelveEntity shelveEntity = boxEntity.getShelve(); + assertThat( shelveEntity.getCreationDate() ).isBetween( anHourAgo, anHourFromNow ); + assertThat( shelveEntity.getId() ).isNull(); + assertThat( shelveEntity.getName() ).isEqualTo( "blue things" ); + assertThat( shelveEntity.getPath() ).isEqualTo( "row5" ); + assertThat( shelveEntity.getStandNumber() ).isEqualTo( 3 ); + assertThat( shelveEntity.getWeightLimit() ).isEqualTo( 10.0d ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ShelveDto.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ShelveDto.java new file mode 100644 index 0000000000..37cfe79036 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ShelveDto.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.mappingcomposition; + +public class ShelveDto { + + private String groupName; + private String path; + private int standNumber; + private double maxWeight; + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public int getStandNumber() { + return standNumber; + } + + public void setStandNumber(int standNumber) { + this.standNumber = standNumber; + } + + public double getMaxWeight() { + return maxWeight; + } + + public void setMaxWeight(double maxWeight) { + this.maxWeight = maxWeight; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ShelveEntity.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ShelveEntity.java new file mode 100644 index 0000000000..39a397c7cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ShelveEntity.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.mappingcomposition; + +import java.util.Date; + +public class ShelveEntity { + + private Date creationDate; + private Long id; + private String name; + + private String path; + private int standNumber; + private double weightLimit; + + public Date getCreationDate() { + return creationDate; + } + + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public int getStandNumber() { + return standNumber; + } + + public void setStandNumber(int standNumber) { + this.standNumber = standNumber; + } + + public double getWeightLimit() { + return weightLimit; + } + + public void setWeightLimit(double weightLimit) { + this.weightLimit = weightLimit; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/StorageMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/StorageMapper.java new file mode 100644 index 0000000000..5136968019 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/StorageMapper.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.mappingcomposition; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ToEntity.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ToEntity.java new file mode 100644 index 0000000000..4cbd94c665 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcomposition/ToEntity.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.mappingcomposition; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.Mapping; + +@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 { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.java new file mode 100644 index 0000000000..a5c05d974d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningBeanMappingMapper.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.mappingcontrol; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CloningBeanMappingMapper { + + CloningBeanMappingMapper INSTANCE = Mappers.getMapper( CloningBeanMappingMapper.class ); + + @BeanMapping(mappingControl = DeepClone.class) + FridgeDTO clone(FridgeDTO in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningListMapper.java new file mode 100644 index 0000000000..5a74527d36 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningListMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +@Mapper(mappingControl = DeepClone.class) +public interface CloningListMapper { + + CloningListMapper INSTANCE = Mappers.getMapper( CloningListMapper.class ); + + CustomerDto clone(CustomerDto in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningMapper.java new file mode 100644 index 0000000000..c1ff968d2c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +@Mapper(mappingControl = DeepClone.class) +public interface CloningMapper { + + CloningMapper INSTANCE = Mappers.getMapper( CloningMapper.class ); + + FridgeDTO clone(FridgeDTO in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ComplexMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ComplexMapper.java new file mode 100644 index 0000000000..768e6117a5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ComplexMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ComplexMapper { + + ComplexMapper INSTANCE = Mappers.getMapper( ComplexMapper.class ); + + @Mapping(target = "beerCount", source = "shelve") + Fridge map(FridgeDTO in); + + default String toBeerCount(ShelveDTO in) { + return in.getCoolBeer().getBeerCount(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/Config.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/Config.java new file mode 100644 index 0000000000..b37c8e4f25 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/Config.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.mappingcontrol; + +import org.mapstruct.MapperConfig; +import org.mapstruct.control.NoComplexMapping; + +/** + * @author Sjaak Derksen + */ +@MapperConfig( mappingControl = NoComplexMapping.class ) +public interface Config { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ConversionMapper.java new file mode 100644 index 0000000000..d4e1c3249d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ConversionMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ConversionMapper { + + ConversionMapper INSTANCE = Mappers.getMapper( ConversionMapper.class ); + + Fridge map(CoolBeerDTO in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CoolBeerDTO.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CoolBeerDTO.java new file mode 100644 index 0000000000..1505d3f291 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CoolBeerDTO.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.mappingcontrol; + +public class CoolBeerDTO { + + private String beerCount; + + public String getBeerCount() { + return beerCount; + } + + public void setBeerCount(String beerCount) { + this.beerCount = beerCount; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CustomerDto.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CustomerDto.java new file mode 100644 index 0000000000..810d53f41e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CustomerDto.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.mappingcontrol; + +import java.util.List; +import java.util.Map; + +/** + * @author Sjaak Derksen + */ +public class CustomerDto { + + private Long id; + private String customerName; + private List orders; + private Map stock; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public List getOrders() { + return orders; + } + + public void setOrders(List orders) { + this.orders = orders; + } + + public Map getStock() { + return stock; + } + + public void setStock(Map stock) { + this.stock = stock; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/DirectMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/DirectMapper.java new file mode 100644 index 0000000000..1529c15eb1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/DirectMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface DirectMapper { + + DirectMapper INSTANCE = Mappers.getMapper( DirectMapper.class ); + + @Mapping(target = "shelve", source = "shelve") + FridgeDTO map(FridgeDTO in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.java new file mode 100644 index 0000000000..eb9f7ea9ce --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndBuiltInMapper.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.mappingcontrol; + +import java.time.ZonedDateTime; +import java.util.Date; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousBuiltInAndBuiltInMapper { + + Target map(Source source); + + class Source { + private final ZonedDateTime time; + + public Source(ZonedDateTime time) { + this.time = time; + } + + public ZonedDateTime getTime() { + return time; + } + } + + class Target { + private final Date time; + + public Target(Date time) { + this.time = time; + } + + public Date getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.java new file mode 100644 index 0000000000..fdd7545da7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousBuiltInAndMethodMapper.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.mappingcontrol; + +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Calendar; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousBuiltInAndMethodMapper { + + Target map(Source source); + + default ZonedDateTime fromInt(int time) { + return ZonedDateTime.ofInstant( Instant.ofEpochMilli( time ), ZoneOffset.UTC ); + } + + class Source { + private final int time; + + public Source(int time) { + this.time = time; + } + + public int getTime() { + return time; + } + } + + class Target { + private Calendar time; + + public Target(Calendar time) { + this.time = time; + } + + public Calendar getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousComplexMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousComplexMapper.java new file mode 100644 index 0000000000..71eaf227ae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousComplexMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(mappingControl = UseDirect.class) +public interface ErroneousComplexMapper { + + ErroneousComplexMapper INSTANCE = Mappers.getMapper( ErroneousComplexMapper.class ); + + @Mapping(target = "beerCount", source = "shelve") + Fridge map(FridgeDTO in); + + default String toBeerCount(ShelveDTO in) { + return in.getCoolBeer().getBeerCount(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousComplexMapperWithConfig.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousComplexMapperWithConfig.java new file mode 100644 index 0000000000..ecb9c58837 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousComplexMapperWithConfig.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(config = Config.class) +public interface ErroneousComplexMapperWithConfig { + + ErroneousComplexMapperWithConfig INSTANCE = Mappers.getMapper( ErroneousComplexMapperWithConfig.class ); + + @Mapping(target = "beerCount", source = "shelve") + Fridge map(FridgeDTO in); + + default String toBeerCount(ShelveDTO in) { + return in.getCoolBeer().getBeerCount(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.java new file mode 100644 index 0000000000..91a94aeca2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionAndMethodMapper.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.mappingcontrol; + +import java.time.Instant; +import java.util.Date; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousConversionAndMethodMapper { + + Target map(Source source); + + default Instant fromDate(int time) { + return Instant.ofEpochMilli( time ); + } + + class Source { + private final int time; + + public Source(int time) { + this.time = time; + } + + public int getTime() { + return time; + } + } + + class Target { + private Date time; + + public Target(Date time) { + this.time = time; + } + + public Date getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionMapper.java new file mode 100644 index 0000000000..a7f09e40e2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousConversionMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper(mappingControl = UseDirect.class) +public interface ErroneousConversionMapper { + + ErroneousConversionMapper INSTANCE = Mappers.getMapper( ErroneousConversionMapper.class ); + + Fridge map(CoolBeerDTO in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousDirectMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousDirectMapper.java new file mode 100644 index 0000000000..672c9bd7c8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousDirectMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(mappingControl = UseComplex.class) +public interface ErroneousDirectMapper { + + ErroneousDirectMapper INSTANCE = Mappers.getMapper( ErroneousDirectMapper.class ); + + @Mapping(target = "shelve", source = "shelve") + FridgeDTO map(FridgeDTO in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.java new file mode 100644 index 0000000000..d7798161c1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndBuiltInMapper.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.mappingcontrol; + +import java.time.ZonedDateTime; +import java.util.Calendar; +import java.util.Date; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousMethodAndBuiltInMapper { + + Target map(Source source); + + default Date fromCalendar(Calendar calendar) { + return calendar != null ? calendar.getTime() : null; + } + + class Source { + private final ZonedDateTime time; + + public Source(ZonedDateTime time) { + this.time = time; + } + + public ZonedDateTime getTime() { + return time; + } + } + + class Target { + private final Date time; + + public Target(Date time) { + this.time = time; + } + + public Date getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.java new file mode 100644 index 0000000000..5a372addd9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodAndConversionMapper.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.mappingcontrol; + +import java.time.Instant; +import java.util.Date; + +import org.mapstruct.Mapper; + +/** + * @author Filip Hrisafov + */ +@Mapper(mappingControl = NoConversion.class) +public interface ErroneousMethodAndConversionMapper { + + Target map(Source source); + + default long fromInstant(Instant value) { + return value != null ? value.getEpochSecond() : null; + } + + class Source { + private final Date time; + + public Source(Date time) { + this.time = time; + } + + public Date getTime() { + return time; + } + } + + class Target { + private long time; + + public Target(long time) { + this.time = time; + } + + public long getTime() { + return time; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodMapper.java new file mode 100644 index 0000000000..276c37a6e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ErroneousMethodMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper(mappingControl = UseDirect.class) +public interface ErroneousMethodMapper { + + ErroneousMethodMapper INSTANCE = Mappers.getMapper( ErroneousMethodMapper.class ); + + @Mapping(target = "beerCount", source = "shelve") + Fridge map(FridgeDTO in); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/Fridge.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/Fridge.java new file mode 100644 index 0000000000..52d0f0e376 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/Fridge.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.mappingcontrol; + +public class Fridge { + + private int beerCount; + + public int getBeerCount() { + return beerCount; + } + + public void setBeerCount(int beerCount) { + this.beerCount = beerCount; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/FridgeDTO.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/FridgeDTO.java new file mode 100644 index 0000000000..0951278619 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/FridgeDTO.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.mappingcontrol; + +public class FridgeDTO { + + private ShelveDTO shelve; + + public ShelveDTO getShelve() { + return shelve; + } + + public void setShelve(ShelveDTO shelve) { + this.shelve = shelve; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java new file mode 100644 index 0000000000..171fe4bc6a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java @@ -0,0 +1,342 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.mappingcontrol; + +import java.util.ArrayList; +import java.util.Collections; +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 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.tuple; + +/** + * @author Sjaak Derksen + */ +@IssueKey("695") +@WithClasses({ + CoolBeerDTO.class, + ShelveDTO.class, + Fridge.class, + FridgeDTO.class, + CustomerDto.class, + OrderItemDto.class, + OrderItemKeyDto.class, + UseDirect.class, + UseComplex.class +}) +public class MappingControlTest { + + /** + * Baseline Test, normal, direct allowed + */ + @ProcessorTest + @WithClasses(DirectMapper.class) + public void directSelectionAllowed() { + + FridgeDTO in = createFridgeDTO(); + FridgeDTO out = DirectMapper.INSTANCE.map( in ); + + assertThat( out ).isNotNull(); + assertThat( out.getShelve() ).isNotNull(); + assertThat( out.getShelve() ).isSameAs( in.getShelve() ); + } + + /** + * Test the deep cloning annotation + */ + @ProcessorTest + @WithClasses(CloningMapper.class) + public void testDeepCloning() { + + FridgeDTO in = createFridgeDTO(); + FridgeDTO out = CloningMapper.INSTANCE.clone( in ); + + assertThat( out ).isNotNull(); + assertThat( out.getShelve() ).isNotNull(); + assertThat( out.getShelve() ).isNotSameAs( in.getShelve() ); + assertThat( out.getShelve().getCoolBeer() ).isNotSameAs( in.getShelve().getCoolBeer() ); + assertThat( out.getShelve().getCoolBeer().getBeerCount() ).isEqualTo( "5" ); + } + + @ProcessorTest + @IssueKey("3135") + @WithClasses(CloningBeanMappingMapper.class) + public void testDeepCloningViaBeanMapping() { + + FridgeDTO in = createFridgeDTO(); + FridgeDTO out = CloningBeanMappingMapper.INSTANCE.clone( in ); + + assertThat( out ).isNotNull(); + assertThat( out.getShelve() ).isNotNull(); + assertThat( out.getShelve() ).isNotSameAs( in.getShelve() ); + assertThat( out.getShelve().getCoolBeer() ).isNotSameAs( in.getShelve().getCoolBeer() ); + assertThat( out.getShelve().getCoolBeer().getBeerCount() ).isEqualTo( "5" ); + } + + /** + * Test the deep cloning annotation with lists + */ + @ProcessorTest + @WithClasses(CloningListMapper.class) + public void testDeepCloningListsAndMaps() { + + CustomerDto in = new CustomerDto(); + in.setId( 10L ); + in.setCustomerName( "Jaques" ); + OrderItemDto order1 = new OrderItemDto(); + order1.setName( "Table" ); + order1.setQuantity( 2L ); + in.setOrders( new ArrayList<>( Collections.singleton( order1 ) ) ); + OrderItemKeyDto key = new OrderItemKeyDto(); + key.setStockNumber( 5 ); + Map stock = new HashMap<>(); + stock.put( key, order1 ); + in.setStock( stock ); + + CustomerDto out = CloningListMapper.INSTANCE.clone( in ); + + assertThat( out.getId() ).isEqualTo( 10 ); + assertThat( out.getCustomerName() ).isEqualTo( "Jaques" ); + assertThat( out.getOrders() ) + .extracting( "name", "quantity" ) + .containsExactly( tuple( "Table", 2L ) ); + assertThat( out.getStock() ).isNotNull(); + assertThat( out.getStock() ).hasSize( 1 ); + + Map.Entry entry = out.getStock().entrySet().iterator().next(); + assertThat( entry.getKey().getStockNumber() ).isEqualTo( 5 ); + assertThat( entry.getValue().getName() ).isEqualTo( "Table" ); + assertThat( entry.getValue().getQuantity() ).isEqualTo( 2L ); + + // check mapper really created new objects + assertThat( out ).isNotSameAs( in ); + assertThat( out.getOrders().get( 0 ) ).isNotSameAs( order1 ); + assertThat( entry.getKey() ).isNotSameAs( key ); + assertThat( entry.getValue() ).isNotSameAs( order1 ); + assertThat( entry.getValue() ).isNotSameAs( out.getOrders().get( 0 ) ); + } + + /** + * This is a nice test. MapStruct looks for a way to map ShelveDto to ShelveDto. + *

        + * MapStruct gets too creative when we allow complex (2 step mappings) to convert if we also allow + * it to forge methods (which is contradiction with the fact that we do not allow methods on this mapper) + */ + @ProcessorTest + @WithClasses(ErroneousDirectMapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousDirectMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"ShelveDTO shelve\" to \"ShelveDTO shelve\". " + + "Consider to declare/implement a mapping method: \"ShelveDTO map(ShelveDTO value)\"." + ) + }) + public void directSelectionNotAllowed() { + } + + /** + * Baseline Test, normal, method allowed + */ + @ProcessorTest + @WithClasses(MethodMapper.class) + public void methodSelectionAllowed() { + Fridge fridge = MethodMapper.INSTANCE.map( createFridgeDTO() ); + + assertThat( fridge ).isNotNull(); + assertThat( fridge.getBeerCount() ).isEqualTo( 5 ); + assertThat( fridge.getBeerCount() ).isEqualTo( 5 ); + } + + @ProcessorTest + @WithClasses(ErroneousMethodMapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMethodMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"ShelveDTO shelve\" to \"int beerCount\". " + + "Consider to declare/implement a mapping method: \"int map(ShelveDTO value)\"." + ) + }) + public void methodSelectionNotAllowed() { + } + + /** + * Baseline Test, normal, conversion allowed + */ + @ProcessorTest + @WithClasses(ConversionMapper.class) + public void conversionSelectionAllowed() { + Fridge fridge = ConversionMapper.INSTANCE.map( createFridgeDTO().getShelve().getCoolBeer() ); + + assertThat( fridge ).isNotNull(); + assertThat( fridge.getBeerCount() ).isEqualTo( 5 ); + } + + @ProcessorTest + @WithClasses(ErroneousConversionMapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousConversionMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 16, + message = "Can't map property \"String beerCount\" to \"int beerCount\". " + + "Consider to declare/implement a mapping method: \"int map(String value)\"." + ) + }) + public void conversionSelectionNotAllowed() { + } + + /** + * Baseline Test, normal, complex mapping allowed + */ + @ProcessorTest + @WithClasses(ComplexMapper.class) + public void complexSelectionAllowed() { + Fridge fridge = ComplexMapper.INSTANCE.map( createFridgeDTO() ); + + assertThat( fridge ).isNotNull(); + assertThat( fridge.getBeerCount() ).isEqualTo( 5 ); + } + + @ProcessorTest + @WithClasses(ErroneousComplexMapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousComplexMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"ShelveDTO shelve\" to \"int beerCount\". " + + "Consider to declare/implement a mapping method: \"int map(ShelveDTO value)\"." + ) + }) + public void complexSelectionNotAllowed() { + } + + @ProcessorTest + @WithClasses({ Config.class, ErroneousComplexMapperWithConfig.class }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousComplexMapperWithConfig.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 17, + message = "Can't map property \"ShelveDTO shelve\" to \"int beerCount\". " + + "Consider to declare/implement a mapping method: \"int map(ShelveDTO value)\"." + ) + }) + public void complexSelectionNotAllowedWithConfig() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousMethodAndBuiltInMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMethodAndBuiltInMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 20, + message = "Can't map property \"ZonedDateTime time\" to \"Date time\". " + + "Consider to declare/implement a mapping method: \"Date map(ZonedDateTime value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepMethodBuiltIdConversion() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousBuiltInAndBuiltInMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousBuiltInAndBuiltInMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Can't map property \"ZonedDateTime time\" to \"Date time\". " + + "Consider to declare/implement a mapping method: \"Date map(ZonedDateTime value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepBuiltInBuiltInConversion() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousBuiltInAndMethodMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousBuiltInAndMethodMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 21, + message = "Can't map property \"int time\" to \"Calendar time\". " + + "Consider to declare/implement a mapping method: \"Calendar map(int value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepBuiltInMethodConversion() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousMethodAndConversionMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousMethodAndConversionMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Can't map property \"Date time\" to \"long time\". " + + "Consider to declare/implement a mapping method: \"long map(Date value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepMethodConversionConversion() { + } + + @ProcessorTest + @IssueKey("3186") + @WithClasses({ + ErroneousConversionAndMethodMapper.class, + NoConversion.class + }) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousConversionAndMethodMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 19, + message = "Can't map property \"int time\" to \"Date time\". " + + "Consider to declare/implement a mapping method: \"Date map(int value)\"." + ) + }) + public void conversionSelectionNotAllowedInTwoStepConversionMethodConversion() { + } + + private FridgeDTO createFridgeDTO() { + FridgeDTO fridgeDTO = new FridgeDTO(); + ShelveDTO shelveDTO = new ShelveDTO(); + CoolBeerDTO coolBeerDTO = new CoolBeerDTO(); + fridgeDTO.setShelve( shelveDTO ); + shelveDTO.setCoolBeer( coolBeerDTO ); + coolBeerDTO.setBeerCount( "5" ); + return fridgeDTO; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MethodMapper.java new file mode 100644 index 0000000000..54e4515c52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MethodMapper.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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface MethodMapper { + + MethodMapper INSTANCE = Mappers.getMapper( MethodMapper.class ); + + @Mapping(target = "beerCount", source = "shelve") + Fridge map(FridgeDTO in); + + default int map(ShelveDTO in) { + return Integer.valueOf( in.getCoolBeer().getBeerCount() ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.java new file mode 100644 index 0000000000..6f05c16bb8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/NoConversion.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.mappingcontrol; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.control.MappingControl; +import org.mapstruct.util.Experimental; + +@Retention(RetentionPolicy.CLASS) +@Experimental +@MappingControl( MappingControl.Use.DIRECT ) +@MappingControl( MappingControl.Use.MAPPING_METHOD ) +@MappingControl( MappingControl.Use.COMPLEX_MAPPING ) +public @interface NoConversion { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemDto.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemDto.java new file mode 100644 index 0000000000..dbaad654d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemDto.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.mappingcontrol; + +/** + * @author Sjaak Derksen + */ +public class OrderItemDto { + + private String name; + private Long quantity; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getQuantity() { + return quantity; + } + + public void setQuantity(Long quantity) { + this.quantity = quantity; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemKeyDto.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemKeyDto.java new file mode 100644 index 0000000000..6154f4450b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemKeyDto.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.mappingcontrol; + +/** + * @author Sjaak Derksen + */ +public class OrderItemKeyDto { + + private long stockNumber; + + public long getStockNumber() { + return stockNumber; + } + + public void setStockNumber(long stockNumber) { + this.stockNumber = stockNumber; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ShelveDTO.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ShelveDTO.java new file mode 100644 index 0000000000..0ddfeb7a2a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/ShelveDTO.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.mappingcontrol; + +public class ShelveDTO { + + private CoolBeerDTO coolBeer; + + public CoolBeerDTO getCoolBeer() { + return coolBeer; + } + + public void setCoolBeer(CoolBeerDTO coolBeer) { + this.coolBeer = coolBeer; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/UseComplex.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/UseComplex.java new file mode 100644 index 0000000000..2424d95300 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/UseComplex.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.mappingcontrol; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.control.MappingControl; +import org.mapstruct.util.Experimental; + +@Retention(RetentionPolicy.CLASS) +@Experimental +@MappingControl( MappingControl.Use.COMPLEX_MAPPING ) +public @interface UseComplex { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/UseDirect.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/UseDirect.java new file mode 100644 index 0000000000..00151d71ed --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/UseDirect.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.mappingcontrol; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import org.mapstruct.control.MappingControl; +import org.mapstruct.util.Experimental; + +@Retention(RetentionPolicy.CLASS) +@Experimental +@MappingControl( MappingControl.Use.DIRECT ) +public @interface UseDirect { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.java new file mode 100644 index 0000000000..1196d773cb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/ErroneousSourceTargetMapper.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.missingignoredsource; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +@Mapper( + unmappedTargetPolicy = ReportingPolicy.IGNORE, + unmappedSourcePolicy = ReportingPolicy.IGNORE) +public interface ErroneousSourceTargetMapper { + + @BeanMapping(ignoreUnmappedSourceProperties = "bar") + Target map(Target source); + + 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/missingignoredsource/MissingIgnoredSourceTest.java b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.java new file mode 100644 index 0000000000..5794126604 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/missingignoredsource/MissingIgnoredSourceTest.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.missingignoredsource; + +import javax.tools.Diagnostic.Kind; + +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; + +public class MissingIgnoredSourceTest { + + @ProcessorTest + @WithClasses({ ErroneousSourceTargetMapper.class }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSourceTargetMapper.class, + kind = Kind.ERROR, + line = 18, + message = "Ignored unknown source property: \"bar\".") + } + ) + public void shouldRaiseErrorDueToMissingIgnoredSourceProperty() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapper.java new file mode 100644 index 0000000000..51ee997615 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapper.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.multisource; + +import java.util.Collection; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface MultiSourceMapper { + + MultiSourceMapper INSTANCE = Mappers.getMapper( MultiSourceMapper.class ); + + Target mapFromPrimitiveAndCollection(int value, Collection elements); + + Target mapFromCollectionAndPrimitive(Collection elements, int value); + + class Target { + private int value; + private Collection elements; + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public Collection getElements() { + return elements; + } + + public void setElements(Collection elements) { + this.elements = elements; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapperTest.java new file mode 100644 index 0000000000..c4e1eaaee4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/multisource/MultiSourceMapperTest.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.multisource; + +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 + */ +public class MultiSourceMapperTest { + + @IssueKey("2005") + @ProcessorTest + @WithClasses({ + MultiSourceMapper.class + }) + void shouldBeAbleToMapFromPrimitiveAndCollectionAsMultiSource() { + MultiSourceMapper.Target target = MultiSourceMapper.INSTANCE.mapFromPrimitiveAndCollection( + 10, + Collections.singleton( "test" ) + ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( 10 ); + assertThat( target.getElements() ).containsExactly( "test" ); + + target = MultiSourceMapper.INSTANCE.mapFromCollectionAndPrimitive( + Collections.singleton( "otherTest" ), + 20 + ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( 20 ); + assertThat( target.getElements() ).containsExactly( "otherTest" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/SuggestMostSimilarNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/SuggestMostSimilarNameTest.java index 1c4c23bb91..079b2f3020 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/SuggestMostSimilarNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/SuggestMostSimilarNameTest.java @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.namesuggestion; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.namesuggestion.erroneous.PersonAgeMapper; import org.mapstruct.ap.test.namesuggestion.erroneous.PersonGarageWrongSourceMapper; import org.mapstruct.ap.test.namesuggestion.erroneous.PersonGarageWrongTargetMapper; import org.mapstruct.ap.test.namesuggestion.erroneous.PersonNameMapper; +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; -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Person.class, PersonDto.class, Garage.class, GarageDto.class, ColorRgb.class, ColorRgbDto.class }) public class SuggestMostSimilarNameTest { - @Test + @ProcessorTest @WithClasses({ PersonAgeMapper.class }) @@ -33,13 +30,13 @@ public class SuggestMostSimilarNameTest { @Diagnostic(type = PersonAgeMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 19, - messageRegExp = ".*Did you mean \"age\"\\?") + message = "No property named \"agee\" exists in source parameter(s). Did you mean \"age\"?") } ) public void testAgeSuggestion() { } - @Test + @ProcessorTest @WithClasses({ PersonNameMapper.class }) @@ -49,13 +46,13 @@ public void testAgeSuggestion() { @Diagnostic(type = PersonNameMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 19, - messageRegExp = ".*Did you mean \"fullName\"\\?") + message = "Unknown property \"fulName\" in result type Person. Did you mean \"fullName\"?") } ) public void testNameSuggestion() { } - @Test + @ProcessorTest @WithClasses({ PersonGarageWrongTargetMapper.class }) @@ -65,17 +62,27 @@ public void testNameSuggestion() { @Diagnostic(type = PersonGarageWrongTargetMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 19, - messageRegExp = "Unknown property \"garage\\.colour\\.rgb\".*Did you mean \"garage\\.color\"\\?"), + message = "Unknown property \"colour\" in type Garage for target name \"garage.colour.rgb\". " + + "Did you mean \"garage.color\"?"), + @Diagnostic(type = PersonGarageWrongTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 20, + messageRegExp = "Unmapped target properties: \"fullName, fullAge\"\\."), @Diagnostic(type = PersonGarageWrongTargetMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 22, - messageRegExp = "Unknown property \"garage\\.colour\".*Did you mean \"garage\\.color\"\\?") + message = "Unknown property \"colour\" in type Garage for target name \"garage.colour\". " + + "Did you mean \"garage.color\"?"), + @Diagnostic(type = PersonGarageWrongTargetMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 23, + messageRegExp = "Unmapped target properties: \"fullName, fullAge\"\\."), } ) public void testGarageTargetSuggestion() { } - @Test + @ProcessorTest @WithClasses({ PersonGarageWrongSourceMapper.class }) @@ -84,12 +91,14 @@ public void testGarageTargetSuggestion() { diagnostics = { @Diagnostic(type = PersonGarageWrongSourceMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 19, - messageRegExp = "No property named \"garage\\.colour\\.rgb\".*Did you mean \"garage\\.color\"\\?"), + line = 21, + message = "No property named \"garage.colour.rgb\" exists in source parameter(s). Did you mean " + + "\"garage.color\"?"), @Diagnostic(type = PersonGarageWrongSourceMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 22, - messageRegExp = "No property named \"garage\\.colour\".*Did you mean \"garage\\.color\"\\?") + line = 28, + message = "No property named \"garage.colour\" exists in source parameter(s). Did you mean \"garage" + + ".color\"?") } ) public void testGarageSourceSuggestion() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonAgeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonAgeMapper.java index 70c1b13c83..9cf3de5e74 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonAgeMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonAgeMapper.java @@ -16,7 +16,7 @@ public interface PersonAgeMapper { PersonAgeMapper MAPPER = Mappers.getMapper( PersonAgeMapper.class ); - @Mapping(source = "agee", target = "fullAge") + @Mapping(target = "fullAge", source = "agee") Person mapPerson(PersonDto dto); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongSourceMapper.java index ed8e6ec901..0fd3a06b0d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongSourceMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongSourceMapper.java @@ -7,6 +7,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Mappings; import org.mapstruct.ap.test.namesuggestion.Person; import org.mapstruct.ap.test.namesuggestion.PersonDto; import org.mapstruct.factory.Mappers; @@ -16,10 +17,18 @@ public interface PersonGarageWrongSourceMapper { PersonGarageWrongSourceMapper MAPPER = Mappers.getMapper( PersonGarageWrongSourceMapper.class ); - @Mapping(source = "garage.colour.rgb", target = "garage.color.rgb") + @Mappings( { + @Mapping( target = "garage.color.rgb", source = "garage.colour.rgb" ), + @Mapping( target = "fullAge", source = "age" ), + @Mapping( target = "fullName", source = "name" ) + } ) Person mapPerson(PersonDto dto); - @Mapping(source = "garage.colour", target = "garage.color") + @Mappings( { + @Mapping( target = "garage.color", source = "garage.colour" ), + @Mapping( target = "fullAge", source = "age" ), + @Mapping( target = "fullName", source = "name" ) + } ) Person mapPersonGarage(PersonDto dto); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongTargetMapper.java index 8a7805237a..fc587e0b71 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonGarageWrongTargetMapper.java @@ -16,9 +16,9 @@ public interface PersonGarageWrongTargetMapper { PersonGarageWrongTargetMapper MAPPER = Mappers.getMapper( PersonGarageWrongTargetMapper.class ); - @Mapping(source = "garage.color.rgb", target = "garage.colour.rgb") + @Mapping(target = "garage.colour.rgb", source = "garage.color.rgb") Person mapPerson(PersonDto dto); - @Mapping(source = "garage.color", target = "garage.colour") + @Mapping(target = "garage.colour", source = "garage.color") Person mapPersonGarage(PersonDto dto); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonNameMapper.java index 11bd389a5f..b14a0d0cf9 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonNameMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/namesuggestion/erroneous/PersonNameMapper.java @@ -16,7 +16,7 @@ public interface PersonNameMapper { PersonNameMapper MAPPER = Mappers.getMapper( PersonNameMapper.class ); - @Mapping(source = "name", target = "fulName") + @Mapping(target = "fulName", source = "name") Person mapPerson(PersonDto dto); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/naming/VariableNamingTest.java b/processor/src/test/java/org/mapstruct/ap/test/naming/VariableNamingTest.java index 2014e11bf6..a8e833ee06 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/naming/VariableNamingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/naming/VariableNamingTest.java @@ -5,20 +5,20 @@ */ package org.mapstruct.ap.test.naming; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; - import java.util.Arrays; +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.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; +import static org.assertj.core.api.Assertions.entry; /** * Test for naming of variables/members which conflict with keywords or parameter names. @@ -27,19 +27,19 @@ */ @WithClasses({ SourceTargetMapper.class, While.class, Break.class, Source.class }) @IssueKey("53") -@RunWith(AnnotationProcessorTestRunner.class) +@ReadsDefaultTimeZone public class VariableNamingTest { - @Test + @ProcessorTest public void shouldGenerateImplementationsOfMethodsWithProblematicVariableNmes() { Source source = new Source(); source.setSomeNumber( 42 ); - source.setValues( Arrays.asList( 42L, 121L ) ); + source.setValues( Arrays.asList( 42L, 121L ) ); - Map map = new HashMap(); - map.put( 42L, new GregorianCalendar( 1980, 0, 1 ).getTime() ); - map.put( 121L, new GregorianCalendar( 2013, 6, 20 ).getTime() ); + Map map = new HashMap<>(); + map.put( 42L, new GregorianCalendar( 1980, Calendar.JANUARY, 1 ).getTime() ); + map.put( 121L, new GregorianCalendar( 2013, Calendar.JULY, 20 ).getTime() ); source.setMap( map ); Break target = SourceTargetMapper.INSTANCE.sourceToBreak( source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/naming/spi/CustomNamingStrategyTest.java b/processor/src/test/java/org/mapstruct/ap/test/naming/spi/CustomNamingStrategyTest.java index d443d952ba..78608dd7ba 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/naming/spi/CustomNamingStrategyTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/naming/spi/CustomNamingStrategyTest.java @@ -5,25 +5,22 @@ */ package org.mapstruct.ap.test.naming.spi; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.spi.AccessorNamingStrategy; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; import org.mapstruct.ap.testutil.WithServiceImplementation; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * Test do demonstrate the usage of custom implementations of {@link AccessorNamingStrategy}. * * @author Andreas Gudian */ -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ GolfPlayer.class, GolfPlayerDto.class, GolfPlayerMapper.class }) @WithServiceImplementation(CustomAccessorNamingStrategy.class) public class CustomNamingStrategyTest { - @Test + @ProcessorTest public void shouldApplyCustomNamingStrategy() { GolfPlayer player = new GolfPlayer() .withName( "Jared" ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/Car.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/Car.java index 25f1d88749..f89a35d830 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/Car.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/Car.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.nestedbeans; import java.util.List; +import java.util.Objects; public class Car { @@ -60,10 +61,10 @@ public boolean equals(Object o) { if ( year != car.year ) { return false; } - if ( name != null ? !name.equals( car.name ) : car.name != null ) { + if ( !Objects.equals( name, car.name ) ) { return false; } - return wheels != null ? wheels.equals( car.wheels ) : car.wheels == null; + return Objects.equals( wheels, car.wheels ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/CarDto.java index 5bc377c2e8..1eed692016 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/CarDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/CarDto.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.nestedbeans; import java.util.List; +import java.util.Objects; public class CarDto { @@ -60,10 +61,10 @@ public boolean equals(Object o) { if ( year != carDto.year ) { return false; } - if ( name != null ? !name.equals( carDto.name ) : carDto.name != null ) { + if ( !Objects.equals( name, carDto.name ) ) { return false; } - return wheels != null ? wheels.equals( carDto.wheels ) : carDto.wheels == null; + return Objects.equals( wheels, carDto.wheels ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DisablingNestedSimpleBeansMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DisablingNestedSimpleBeansMappingTest.java index 3236429fce..7331a5cc2d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DisablingNestedSimpleBeansMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DisablingNestedSimpleBeansMappingTest.java @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.nestedbeans; -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 Filip Hrisafov @@ -22,7 +20,6 @@ Roof.class, RoofDto.class, RoofType.class, ExternalRoofType.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class DisablingNestedSimpleBeansMappingTest { @WithClasses({ @@ -33,12 +30,12 @@ public class DisablingNestedSimpleBeansMappingTest { @Diagnostic(type = ErroneousDisabledHouseMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "Can't map property \".*\\.Roof roof\" to \".*\\.RoofDto roof\"\\. Consider to " + - "declare/implement a mapping method: \".*\\.RoofDto map\\(.*\\.Roof value\\)\"\\." + message = "Can't map property \"Roof roof\" to \"RoofDto roof\". " + + "Consider to declare/implement a mapping method: \"RoofDto map(Roof value)\"." ) }) - @Test - public void shouldUseDisabledMethodGenerationOnMapper() throws Exception { + @ProcessorTest + public void shouldUseDisabledMethodGenerationOnMapper() { } @WithClasses({ @@ -50,11 +47,11 @@ public void shouldUseDisabledMethodGenerationOnMapper() throws Exception { @Diagnostic(type = ErroneousDisabledViaConfigHouseMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "Can't map property \".*\\.Roof roof\" to \".*\\.RoofDto roof\"\\. Consider to " + - "declare/implement a mapping method: \".*\\.RoofDto map\\(.*\\.Roof value\\)\"\\." + message = "Can't map property \"Roof roof\" to \"RoofDto roof\". " + + "Consider to declare/implement a mapping method: \"RoofDto map(Roof value)\"." ) }) - @Test - public void shouldUseDisabledMethodGenerationOnMapperConfig() throws Exception { + @ProcessorTest + public void shouldUseDisabledMethodGenerationOnMapperConfig() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DottedErrorMessageTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DottedErrorMessageTest.java index 1d684ec502..571f3feade 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DottedErrorMessageTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/DottedErrorMessageTest.java @@ -5,66 +5,76 @@ */ package org.mapstruct.ap.test.nestedbeans; -import org.junit.Test; - -import org.junit.runner.RunWith; import org.mapstruct.ap.test.nestedbeans.unmappable.BaseCollectionElementPropertyMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepListMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapKeyMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapValueMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepNestingMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.BaseValuePropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.Car; +import org.mapstruct.ap.test.nestedbeans.unmappable.CarDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Cat; +import org.mapstruct.ap.test.nestedbeans.unmappable.CatDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Color; +import org.mapstruct.ap.test.nestedbeans.unmappable.ColorDto; import org.mapstruct.ap.test.nestedbeans.unmappable.Computer; import org.mapstruct.ap.test.nestedbeans.unmappable.ComputerDto; import org.mapstruct.ap.test.nestedbeans.unmappable.Dictionary; import org.mapstruct.ap.test.nestedbeans.unmappable.DictionaryDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.ExternalRoofType; import org.mapstruct.ap.test.nestedbeans.unmappable.ForeignWord; import org.mapstruct.ap.test.nestedbeans.unmappable.ForeignWordDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.Cat; -import org.mapstruct.ap.test.nestedbeans.unmappable.CatDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.House; +import org.mapstruct.ap.test.nestedbeans.unmappable.HouseDto; import org.mapstruct.ap.test.nestedbeans.unmappable.Info; import org.mapstruct.ap.test.nestedbeans.unmappable.InfoDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Roof; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofType; import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableCollectionElementPropertyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableDeepListMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableDeepMapKeyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableDeepMapValueMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableEnumMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableValuePropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.User; +import org.mapstruct.ap.test.nestedbeans.unmappable.UserDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Wheel; +import org.mapstruct.ap.test.nestedbeans.unmappable.WheelDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Word; +import org.mapstruct.ap.test.nestedbeans.unmappable.WordDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableSourceCollectionElementPropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableSourceDeepListMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableSourceDeepMapKeyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableSourceDeepMapValueMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableSourceDeepNestingMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableSourceEnumMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableSourceValuePropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableTargetCollectionElementPropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableTargetDeepListMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableTargetDeepMapKeyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableTargetDeepMapValueMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableTargetDeepNestingMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableTargetValuePropertyMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.ignore.UnmappableIgnoreCollectionElementPropertyMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.ignore.UnmappableIgnoreDeepListMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.ignore.UnmappableIgnoreDeepMapKeyMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.ignore.UnmappableIgnoreDeepMapValueMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.ignore.UnmappableIgnoreDeepNestingMapper; import org.mapstruct.ap.test.nestedbeans.unmappable.ignore.UnmappableIgnoreValuePropertyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableWarnCollectionElementPropertyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableWarnDeepListMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableWarnDeepMapKeyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableWarnDeepMapValueMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableWarnDeepNestingMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableWarnValuePropertyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.UserDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.User; -import org.mapstruct.ap.test.nestedbeans.unmappable.WheelDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.Wheel; -import org.mapstruct.ap.test.nestedbeans.unmappable.Car; -import org.mapstruct.ap.test.nestedbeans.unmappable.CarDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.ExternalRoofType; -import org.mapstruct.ap.test.nestedbeans.unmappable.House; -import org.mapstruct.ap.test.nestedbeans.unmappable.HouseDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.Color; -import org.mapstruct.ap.test.nestedbeans.unmappable.ColorDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.Roof; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofType; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.erroneous.UnmappableDeepNestingMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.Word; -import org.mapstruct.ap.test.nestedbeans.unmappable.WordDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableSourceWarnCollectionElementPropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableSourceWarnDeepListMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableSourceWarnDeepMapKeyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableSourceWarnDeepMapValueMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableSourceWarnDeepNestingMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableSourceWarnValuePropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableTargetWarnCollectionElementPropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableTargetWarnDeepListMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableTargetWarnDeepMapKeyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableTargetWarnDeepMapValueMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableTargetWarnDeepNestingMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.warn.UnmappableTargetWarnValuePropertyMapper; +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; @WithClasses({ Car.class, CarDto.class, Color.class, ColorDto.class, @@ -82,7 +92,6 @@ BaseDeepNestingMapper.class, BaseValuePropertyMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class DottedErrorMessageTest { private static final String PROPERTY = "property"; @@ -90,177 +99,356 @@ public class DottedErrorMessageTest { private static final String MAP_KEY = "Map key"; private static final String MAP_VALUE = "Map value"; - @Test + @ProcessorTest + @WithClasses({ + UnmappableTargetDeepNestingMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = UnmappableTargetDeepNestingMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "Unmapped target property: \"rgb\". Mapping from " + PROPERTY + + " \"Color house.roof.color\" to \"ColorDto house.roof.color\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepNestingMapper'.") + } + ) + public void testTargetDeepNestedBeans() { + } + + @ProcessorTest + @WithClasses({ + UnmappableTargetDeepListMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = UnmappableTargetDeepListMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "Unmapped target property: \"left\". Mapping from " + COLLECTION_ELEMENT + + " \"Wheel car.wheels\" to \"WheelDto car.wheels\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepListMapper'.") + } + ) + public void testTargetIterables() { + } + + @ProcessorTest + @WithClasses({ + UnmappableTargetDeepMapKeyMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = UnmappableTargetDeepMapKeyMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_KEY + + " \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapKeyMapper'.") + } + ) + public void testTargetMapKeys() { + } + + @ProcessorTest + @WithClasses({ + UnmappableTargetDeepMapValueMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = UnmappableTargetDeepMapValueMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_VALUE + + " \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapValueMapper'.") + } + ) + public void testTargetMapValues() { + } + + @ProcessorTest + @WithClasses({ + UnmappableTargetCollectionElementPropertyMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = UnmappableTargetCollectionElementPropertyMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "Unmapped target property: \"color\". Mapping from " + PROPERTY + + " \"Info computers[].info\" to \"InfoDto computers[].info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseCollectionElementPropertyMapper'.") + } + ) + public void testTargetCollectionElementProperty() { + } + + @ProcessorTest + @WithClasses({ + UnmappableTargetValuePropertyMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = UnmappableTargetValuePropertyMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 14, + message = "Unmapped target property: \"color\". Mapping from " + PROPERTY + + " \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseValuePropertyMapper'.") + } + ) + public void testTargetMapValueProperty() { + } + + @IssueKey( "2788" ) + @ProcessorTest @WithClasses({ - UnmappableDeepNestingMapper.class + UnmappableSourceDeepNestingMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseDeepNestingMapper.class, + @Diagnostic(type = UnmappableSourceDeepNestingMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, - messageRegExp = "Unmapped target property: \"rgb\"\\. Mapping from " + PROPERTY + - " \".*Color house\\.roof\\.color\" to \".*ColorDto house\\.roof\\.color\"\\.") + line = 16, + message = "Unmapped source property: \"cmyk\". Mapping from " + PROPERTY + + " \"Color house.roof.color\" to \"ColorDto house.roof.color\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepNestingMapper'.") } ) - public void testDeepNestedBeans() { + public void testSourceDeepNestedBeans() { } - @Test + @IssueKey( "2788" ) + @ProcessorTest @WithClasses({ - UnmappableDeepListMapper.class + UnmappableSourceDeepListMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseDeepListMapper.class, + @Diagnostic(type = UnmappableSourceDeepListMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, - messageRegExp = "Unmapped target property: \"left\"\\. Mapping from " + COLLECTION_ELEMENT + - " \".*Wheel car\\.wheels\" to \".*WheelDto car\\.wheels\"\\.") + line = 16, + message = "Unmapped source property: \"right\". Mapping from " + COLLECTION_ELEMENT + + " \"Wheel car.wheels\" to \"WheelDto car.wheels\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepListMapper'.") } ) - public void testIterables() { + public void testSourceIterables() { } - @Test + @IssueKey( "2788" ) + @ProcessorTest @WithClasses({ - UnmappableDeepMapKeyMapper.class + UnmappableSourceDeepMapKeyMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseDeepMapKeyMapper.class, + @Diagnostic(type = UnmappableSourceDeepMapKeyMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, - messageRegExp = "Unmapped target property: \"pronunciation\"\\. Mapping from " + MAP_KEY + - " \".*Word dictionary\\.wordMap\\{:key\\}\" to \".*WordDto dictionary\\.wordMap\\{:key\\}\"\\.") + line = 16, + message = "Unmapped source property: \"meaning\". Mapping from " + MAP_KEY + + " \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapKeyMapper'.") } ) - public void testMapKeys() { + public void testSourceMapKeys() { } - @Test + @IssueKey( "2788" ) + @ProcessorTest @WithClasses({ - UnmappableDeepMapValueMapper.class + UnmappableSourceDeepMapValueMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseDeepMapValueMapper.class, + @Diagnostic(type = UnmappableSourceDeepMapValueMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, - messageRegExp = "Unmapped target property: \"pronunciation\"\\. Mapping from " + MAP_VALUE + - " \".*ForeignWord dictionary\\.wordMap\\{:value\\}\" " + - "to \".*ForeignWordDto dictionary\\.wordMap\\{:value\\}\"\\.") + line = 16, + message = "Unmapped source property: \"meaning\". Mapping from " + MAP_VALUE + + " \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapValueMapper'.") } ) - public void testMapValues() { + public void testSourceMapValues() { } - @Test + @IssueKey( "2788" ) + @ProcessorTest @WithClasses({ - UnmappableCollectionElementPropertyMapper.class + UnmappableSourceCollectionElementPropertyMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseCollectionElementPropertyMapper.class, + @Diagnostic(type = UnmappableSourceCollectionElementPropertyMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, - messageRegExp = "Unmapped target property: \"color\"\\. Mapping from " + PROPERTY + - " \".*Info computers\\[\\].info\" to \".*InfoDto computers\\[\\].info\"\\.") + line = 16, + message = "Unmapped source property: \"size\". Mapping from " + PROPERTY + + " \"Info computers[].info\" to \"InfoDto computers[].info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseCollectionElementPropertyMapper'.") } ) - public void testCollectionElementProperty() { + public void testSourceCollectionElementProperty() { } - @Test + @IssueKey( "2788" ) + @ProcessorTest @WithClasses({ - UnmappableValuePropertyMapper.class + UnmappableSourceValuePropertyMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = BaseValuePropertyMapper.class, + @Diagnostic(type = UnmappableSourceValuePropertyMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, - line = 10, - messageRegExp = "Unmapped target property: \"color\"\\. Mapping from " + PROPERTY + - " \".*Info catNameMap\\{:value\\}.info\" to \".*InfoDto catNameMap\\{:value\\}.info\"\\.") + line = 16, + message = "Unmapped source property: \"size\". Mapping from " + PROPERTY + + " \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseValuePropertyMapper'.") } ) - public void testMapValueProperty() { + public void testSourceMapValueProperty() { } - @Test + @ProcessorTest @WithClasses({ - UnmappableEnumMapper.class + UnmappableSourceEnumMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, diagnostics = { - @Diagnostic(type = UnmappableEnumMapper.class, + @Diagnostic(type = UnmappableSourceEnumMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 25, - messageRegExp = "The following constants from the property \".*RoofType house\\.roof\\.type\" enum " + - "have no corresponding constant in the \".*ExternalRoofType house\\.roof\\.type\" enum and must " + - "be be mapped via adding additional mappings: NORMAL\\." + message = + "The following constants from the property \"RoofType house.roof.type\" enum " + + "have no corresponding constant in the \"ExternalRoofType house.roof.type\" enum and must " + + "be be mapped via adding additional mappings: NORMAL." ) } ) - public void testMapEnumProperty() { + public void testSourceMapEnumProperty() { } - @Test + @ProcessorTest @WithClasses({ - UnmappableWarnDeepNestingMapper.class, - UnmappableWarnDeepListMapper.class, - UnmappableWarnDeepMapKeyMapper.class, - UnmappableWarnDeepMapValueMapper.class, - UnmappableWarnCollectionElementPropertyMapper.class, - UnmappableWarnValuePropertyMapper.class + UnmappableTargetWarnDeepNestingMapper.class, + UnmappableTargetWarnDeepListMapper.class, + UnmappableTargetWarnDeepMapKeyMapper.class, + UnmappableTargetWarnDeepMapValueMapper.class, + UnmappableTargetWarnCollectionElementPropertyMapper.class, + UnmappableTargetWarnValuePropertyMapper.class }) @ExpectedCompilationOutcome( value = CompilationResult.SUCCEEDED, diagnostics = { - @Diagnostic(type = BaseDeepNestingMapper.class, + @Diagnostic(type = UnmappableTargetWarnDeepNestingMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, - messageRegExp = "Unmapped target property: \"rgb\"\\. Mapping from " + PROPERTY + - " \".*Color house\\.roof\\.color\" to \".*ColorDto house\\.roof\\.color\"\\."), - @Diagnostic(type = BaseDeepListMapper.class, + line = 16, + message = "Unmapped target property: \"rgb\". Mapping from " + PROPERTY + + " \"Color house.roof.color\" to \"ColorDto house.roof.color\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepNestingMapper'."), + @Diagnostic(type = UnmappableTargetWarnDeepListMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, - messageRegExp = "Unmapped target property: \"left\"\\. Mapping from " + COLLECTION_ELEMENT + - " \".*Wheel car\\.wheels\" to \".*WheelDto car\\.wheels\"\\."), - @Diagnostic(type = BaseDeepMapKeyMapper.class, + line = 16, + message = "Unmapped target property: \"left\". Mapping from " + COLLECTION_ELEMENT + + " \"Wheel car.wheels\" to \"WheelDto car.wheels\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepListMapper'."), + @Diagnostic(type = UnmappableTargetWarnDeepMapKeyMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, - messageRegExp = "Unmapped target property: \"pronunciation\"\\. Mapping from " + MAP_KEY + - " \".*Word dictionary\\.wordMap\\{:key\\}\" to \".*WordDto dictionary\\.wordMap\\{:key\\}\"\\."), - @Diagnostic(type = BaseDeepMapValueMapper.class, + line = 16, + message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_KEY + + " \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapKeyMapper'."), + @Diagnostic(type = UnmappableTargetWarnDeepMapValueMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, - messageRegExp = "Unmapped target property: \"pronunciation\"\\. Mapping from " + MAP_VALUE + - " \".*ForeignWord dictionary\\.wordMap\\{:value\\}\" " + - "to \".*ForeignWordDto dictionary\\.wordMap\\{:value\\}\"\\."), - @Diagnostic(type = BaseCollectionElementPropertyMapper.class, + line = 16, + message = "Unmapped target property: \"pronunciation\". Mapping from " + MAP_VALUE + + " \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapValueMapper'."), + @Diagnostic(type = UnmappableTargetWarnCollectionElementPropertyMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, - messageRegExp = "Unmapped target property: \"color\"\\. Mapping from " + PROPERTY + - " \".*Info computers\\[\\].info\" to \".*InfoDto computers\\[\\].info\"\\."), - @Diagnostic(type = BaseValuePropertyMapper.class, + line = 16, + message = "Unmapped target property: \"color\". Mapping from " + PROPERTY + + " \"Info computers[].info\" to \"InfoDto computers[].info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseCollectionElementPropertyMapper'."), + @Diagnostic(type = UnmappableTargetWarnValuePropertyMapper.class, kind = javax.tools.Diagnostic.Kind.WARNING, - line = 10, - messageRegExp = "Unmapped target property: \"color\"\\. Mapping from " + PROPERTY + - " \".*Info catNameMap\\{:value\\}.info\" to \".*InfoDto catNameMap\\{:value\\}.info\"\\.") + line = 16, + message = "Unmapped target property: \"color\". Mapping from " + PROPERTY + + " \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseValuePropertyMapper'.") } ) public void testWarnUnmappedTargetProperties() { } - @Test + @IssueKey( "2788" ) + @ProcessorTest + @WithClasses({ + UnmappableSourceWarnDeepNestingMapper.class, + UnmappableSourceWarnDeepListMapper.class, + UnmappableSourceWarnDeepMapKeyMapper.class, + UnmappableSourceWarnDeepMapValueMapper.class, + UnmappableSourceWarnCollectionElementPropertyMapper.class, + UnmappableSourceWarnValuePropertyMapper.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.SUCCEEDED, + diagnostics = { + @Diagnostic(type = UnmappableSourceWarnDeepNestingMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 16, + message = "Unmapped source property: \"cmyk\". Mapping from " + PROPERTY + + " \"Color house.roof.color\" to \"ColorDto house.roof.color\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepNestingMapper'."), + @Diagnostic(type = UnmappableSourceWarnDeepListMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 16, + message = "Unmapped source property: \"right\". Mapping from " + COLLECTION_ELEMENT + + " \"Wheel car.wheels\" to \"WheelDto car.wheels\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepListMapper'."), + @Diagnostic(type = UnmappableSourceWarnDeepMapKeyMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 16, + message = "Unmapped source property: \"meaning\". Mapping from " + MAP_KEY + + " \"Word dictionary.wordMap{:key}\" to \"WordDto dictionary.wordMap{:key}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapKeyMapper'."), + @Diagnostic(type = UnmappableSourceWarnDeepMapValueMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 16, + message = "Unmapped source property: \"meaning\". Mapping from " + MAP_VALUE + + " \"ForeignWord dictionary.wordMap{:value}\" to \"ForeignWordDto dictionary.wordMap{:value}\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseDeepMapValueMapper'."), + @Diagnostic(type = UnmappableSourceWarnCollectionElementPropertyMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 16, + message = "Unmapped source property: \"size\". Mapping from " + PROPERTY + + " \"Info computers[].info\" to \"InfoDto computers[].info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseCollectionElementPropertyMapper'."), + @Diagnostic(type = UnmappableSourceWarnValuePropertyMapper.class, + kind = javax.tools.Diagnostic.Kind.WARNING, + line = 16, + message = "Unmapped source property: \"size\". Mapping from " + PROPERTY + + " \"Info catNameMap{:value}.info\" to \"InfoDto catNameMap{:value}.info\"." + + " Occured at 'UserDto userToUserDto(User user)' in 'BaseValuePropertyMapper'.") + } + ) + public void testWarnUnmappedSourceProperties() { + } + + @ProcessorTest @WithClasses({ UnmappableIgnoreDeepNestingMapper.class, UnmappableIgnoreDeepListMapper.class, diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/House.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/House.java index ada8a2b6e7..8badaae653 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/House.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/House.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans; +import java.util.Objects; + public class House { private String name; @@ -60,10 +62,10 @@ public boolean equals(Object o) { if ( year != house.year ) { return false; } - if ( name != null ? !name.equals( house.name ) : house.name != null ) { + if ( !Objects.equals( name, house.name ) ) { return false; } - return roof != null ? roof.equals( house.roof ) : house.roof == null; + return Objects.equals( roof, house.roof ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/HouseDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/HouseDto.java index 1d92eb7e4f..71beedaa93 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/HouseDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/HouseDto.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans; +import java.util.Objects; + public class HouseDto { private String name; @@ -58,10 +60,10 @@ public boolean equals(Object o) { if ( year != houseDto.year ) { return false; } - if ( name != null ? !name.equals( houseDto.name ) : houseDto.name != null ) { + if ( !Objects.equals( name, houseDto.name ) ) { return false; } - return roof != null ? roof.equals( houseDto.roof ) : houseDto.roof == null; + return Objects.equals( roof, houseDto.roof ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/MultipleForgedMethodsTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/MultipleForgedMethodsTest.java index 655b49a310..9231ed46e2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/MultipleForgedMethodsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/MultipleForgedMethodsTest.java @@ -5,9 +5,9 @@ */ package org.mapstruct.ap.test.nestedbeans; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; +import java.util.Arrays; +import java.util.HashMap; + import org.mapstruct.ap.test.nestedbeans.maps.AntonymsDictionary; import org.mapstruct.ap.test.nestedbeans.maps.AntonymsDictionaryDto; import org.mapstruct.ap.test.nestedbeans.maps.AutoMapMapper; @@ -16,26 +16,24 @@ import org.mapstruct.ap.test.nestedbeans.multiplecollections.Garage; import org.mapstruct.ap.test.nestedbeans.multiplecollections.GarageDto; import org.mapstruct.ap.test.nestedbeans.multiplecollections.MultipleListMapper; +import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import java.util.Arrays; -import java.util.HashMap; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * This test is for a case when several identical methods could be generated, what is an easy edge case to miss. */ -@RunWith(AnnotationProcessorTestRunner.class) public class MultipleForgedMethodsTest { @WithClasses({ Word.class, WordDto.class, AntonymsDictionaryDto.class, AntonymsDictionary.class, AutoMapMapper.class }) - @Test + @ProcessorTest public void testNestedMapsAutoMap() { - HashMap dtoAntonyms = new HashMap(); - HashMap entityAntonyms = new HashMap(); + HashMap dtoAntonyms = new HashMap<>(); + HashMap entityAntonyms = new HashMap<>(); String[] words = { "black", "good", "up", "left", "fast" }; String[] antonyms = { "white", "bad", "down", "right", "slow" }; @@ -52,8 +50,10 @@ public void testNestedMapsAutoMap() { AntonymsDictionary mappedAntonymsDictionary = AutoMapMapper.INSTANCE.entityToDto( new AntonymsDictionaryDto( dtoAntonyms ) ); - Assert.assertEquals( "Mapper did not map dto to entity correctly", new AntonymsDictionary( entityAntonyms ), - mappedAntonymsDictionary + assertEquals( + new AntonymsDictionary( entityAntonyms ), + mappedAntonymsDictionary, + "Mapper did not map dto to entity correctly" ); } @@ -61,7 +61,7 @@ public void testNestedMapsAutoMap() { MultipleListMapper.class, Garage.class, GarageDto.class, Car.class, CarDto.class, Wheel.class, WheelDto.class }) - @Test + @ProcessorTest public void testMultipleCollections() { GarageDto dto = new GarageDto( Arrays.asList( new CarDto( @@ -97,7 +97,7 @@ public void testMultipleCollections() { GarageDto mappedDto = MultipleListMapper.INSTANCE.convert( entity ); - Assert.assertEquals( "Mapper did not map entity to dto correctly", dto, mappedDto ); + assertEquals( dto, mappedDto, "Mapper did not map entity to dto correctly" ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/NestedSimpleBeansMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/NestedSimpleBeansMappingTest.java index 08c5e2ec17..b78da21860 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/NestedSimpleBeansMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/NestedSimpleBeansMappingTest.java @@ -6,15 +6,12 @@ package org.mapstruct.ap.test.nestedbeans; import java.util.List; -import java.util.function.Function; import java.util.stream.Collectors; import org.assertj.core.groups.Tuple; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; +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; @@ -34,18 +31,17 @@ UserDtoMapperSmart.class, UserDtoUpdateMapperSmart.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class NestedSimpleBeansMappingTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( UserDtoMapperClassic.class, UserDtoMapperSmart.class, UserDtoUpdateMapperSmart.class ); - @Test - public void shouldHaveAllFields() throws Exception { + @ProcessorTest + public void shouldHaveAllFields() { // If this test fails that means something new was added to the structure of the User/UserDto. // Make sure that the other tests are also updated (the new field is asserted) String[] userFields = new String[] { "name", "car", "secondCar", "house" }; @@ -69,7 +65,7 @@ public void shouldHaveAllFields() throws Exception { assertThat( RoofDto.class ).hasOnlyDeclaredFields( roofFields ); } - @Test + @ProcessorTest public void shouldMapNestedBeans() { User user = TestData.createUser(); @@ -81,7 +77,7 @@ public void shouldMapNestedBeans() { assertUserDto( smartMapping, user ); } - @Test + @ProcessorTest public void shouldMapUpdateNestedBeans() { User user = TestData.createUser(); @@ -125,12 +121,9 @@ private static void assertCar(CarDto carDto, Car car) { } private static void assertWheels(List wheelDtos, List wheels) { - List wheelTuples = wheels.stream().map( new Function() { - @Override - public Tuple apply(Wheel wheel) { - return tuple( wheel.isFront(), wheel.isRight() ); - } - } ).collect( Collectors.toList() ); + List wheelTuples = wheels.stream() + .map( wheel -> tuple( wheel.isFront(), wheel.isRight() ) ) + .collect( Collectors.toList() ); assertThat( wheelDtos ) .extracting( "front", "right" ) .containsExactlyElementsOf( wheelTuples ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/RecursionTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/RecursionTest.java index 90f5553278..e8caa784b1 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/RecursionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/RecursionTest.java @@ -9,24 +9,21 @@ import java.util.Iterator; import java.util.List; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.nestedbeans.recursive.RecursionMapper; import org.mapstruct.ap.test.nestedbeans.recursive.TreeRecursionMapper; 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.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; -@RunWith(AnnotationProcessorTestRunner.class) public class RecursionTest { @WithClasses({ RecursionMapper.class }) - @Test + @ProcessorTest @IssueKey("1103") public void testRecursiveAutoMap() { RecursionMapper.RootDto rootDto = new RecursionMapper.RootDto( @@ -61,7 +58,7 @@ private void assertChildEquals(RecursionMapper.ChildDto childDto, RecursionMappe @WithClasses({ TreeRecursionMapper.class }) - @Test + @ProcessorTest @IssueKey("1103") public void testRecursiveTreeAutoMap() { TreeRecursionMapper.RootDto rootDto = new TreeRecursionMapper.RootDto( diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/RoofDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/RoofDto.java index eb5702f0c2..5721d3f6f7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/RoofDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/RoofDto.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans; +import java.util.Objects; + public class RoofDto { private String color; private ExternalRoofType type; @@ -48,7 +50,7 @@ public boolean equals(Object o) { return false; } - return color != null ? color.equals( roofDto.color ) : roofDto.color == null; + return Objects.equals( color, roofDto.color ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/User.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/User.java index 48eb94d61b..11ffc5b889 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/User.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/User.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans; +import java.util.Objects; + public class User { private String name; @@ -64,13 +66,13 @@ public boolean equals(Object o) { User user = (User) o; - if ( name != null ? !name.equals( user.name ) : user.name != null ) { + if ( !Objects.equals( name, user.name ) ) { return false; } - if ( car != null ? !car.equals( user.car ) : user.car != null ) { + if ( !Objects.equals( car, user.car ) ) { return false; } - return house != null ? house.equals( user.house ) : user.house == null; + return Objects.equals( house, user.house ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/UserDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/UserDto.java index 105c84e74b..c496e23529 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/UserDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/UserDto.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans; +import java.util.Objects; + public class UserDto { private String name; @@ -64,13 +66,13 @@ public boolean equals(Object o) { UserDto userDto = (UserDto) o; - if ( name != null ? !name.equals( userDto.name ) : userDto.name != null ) { + if ( !Objects.equals( name, userDto.name ) ) { return false; } - if ( car != null ? !car.equals( userDto.car ) : userDto.car != null ) { + if ( !Objects.equals( car, userDto.car ) ) { return false; } - return house != null ? house.equals( userDto.house ) : userDto.house == null; + return Objects.equals( house, userDto.house ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exceptions/EntityFactory.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exceptions/EntityFactory.java index 1276cbfc39..6e0c5fd957 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exceptions/EntityFactory.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exceptions/EntityFactory.java @@ -21,10 +21,7 @@ public T createEntity(@TargetType Class entityClass) throws MappingExcept try { entity = entityClass.newInstance(); } - catch ( IllegalAccessException exception ) { - throw new MappingException( "Rare exception thrown, refer to stack trace", exception ); - } - catch ( InstantiationException exception ) { + catch ( IllegalAccessException | InstantiationException exception ) { throw new MappingException( "Rare exception thrown, refer to stack trace", exception ); } catch ( Exception exception ) { diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exceptions/NestedMappingsWithExceptionTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exceptions/NestedMappingsWithExceptionTest.java index 509ca1e146..2a5f10ad22 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exceptions/NestedMappingsWithExceptionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exceptions/NestedMappingsWithExceptionTest.java @@ -5,15 +5,13 @@ */ package org.mapstruct.ap.test.nestedbeans.exceptions; -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.nestedbeans.exceptions._target.DeveloperDto; import org.mapstruct.ap.test.nestedbeans.exceptions._target.ProjectDto; import org.mapstruct.ap.test.nestedbeans.exceptions.source.Developer; import org.mapstruct.ap.test.nestedbeans.exceptions.source.Project; 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; /** * @author Filip Hrisafov @@ -28,10 +26,9 @@ EntityFactory.class }) @IssueKey("1304") -@RunWith(AnnotationProcessorTestRunner.class) public class NestedMappingsWithExceptionTest { - @Test + @ProcessorTest public void shouldGenerateCodeThatCompiles() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/ErroneousJavaInternalTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/ErroneousJavaInternalTest.java index cd05ad64be..ae4a7d945a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/ErroneousJavaInternalTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/ErroneousJavaInternalTest.java @@ -5,14 +5,12 @@ */ package org.mapstruct.ap.test.nestedbeans.exclusions; -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 Filip Hrisafov @@ -22,7 +20,6 @@ Target.class, ErroneousJavaInternalMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1154") public class ErroneousJavaInternalTest { @@ -31,28 +28,27 @@ public class ErroneousJavaInternalTest { @Diagnostic(type = ErroneousJavaInternalMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "Can't map property \".*MyType date\" to \"java\\.util\\.Date date\"\\. Consider to " + - "declare/implement a mapping method: \"java\\.util\\.Date map\\(.*MyType value\\)\"\\."), + message = "Can't map property \"Source.MyType date\" to \"Date date\". " + + "Consider to declare/implement a mapping method: \"Date map(Source.MyType value)\"."), @Diagnostic(type = ErroneousJavaInternalMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "Can't map property \".*MyType calendar\" to \"java\\.util\\.GregorianCalendar " + - "calendar\"\\. Consider to declare/implement a mapping method: \"java\\.util\\.GregorianCalendar " + - "map\\(.*MyType value\\)\"\\."), + message = "Can't map property \"Source.MyType calendar\" to \"GregorianCalendar calendar\". " + + "Consider to declare/implement a mapping method: \"GregorianCalendar map(Source.MyType value)\"."), @Diagnostic(type = ErroneousJavaInternalMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "Can't map property \".*List<.*MyType> types\" to \".*List<.*String> types\"\\" + - ". Consider to declare/implement a mapping method: \".*List<.*String> map\\(.*List<.*MyType> " + - "value\\)\"\\."), + message = "Can't map property \"List types\" to \"List types\". " + + "Consider to declare/implement a mapping method: \"List map(List value)\"."), @Diagnostic(type = ErroneousJavaInternalMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 16, - messageRegExp = "Can't map property \".*List<.*MyType> nestedMyType\\.deepNestedType\\.types\" to \"" + - ".*List<.*String> nestedMyType\\.deepNestedType\\.types\"\\. Consider to declare/implement a " + - "mapping method: \".*List<.*String> map\\(.*List<.*MyType> value\\)\"\\.") + message = "Can't map property \"List nestedMyType.deepNestedType.types\" to " + + "\"List nestedMyType.deepNestedType.types\". " + + "Consider to declare/implement a mapping method: " + + "\"List map(List value)\".") }) - @Test - public void shouldNotNestIntoJavaPackageObjects() throws Exception { + @ProcessorTest + public void shouldNotNestIntoJavaPackageObjects() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/custom/ErroneousCustomExclusionTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/custom/ErroneousCustomExclusionTest.java index b34a8d451c..4fb90cf6ab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/custom/ErroneousCustomExclusionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/custom/ErroneousCustomExclusionTest.java @@ -5,15 +5,13 @@ */ package org.mapstruct.ap.test.nestedbeans.exclusions.custom; -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.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.AnnotationProcessorTestRunner; /** * @author Filip Hrisafov @@ -24,7 +22,6 @@ ErroneousCustomExclusionMapper.class }) @WithServiceImplementation( CustomMappingExclusionProvider.class ) -@RunWith(AnnotationProcessorTestRunner.class) @IssueKey("1154") public class ErroneousCustomExclusionTest { @@ -33,12 +30,11 @@ public class ErroneousCustomExclusionTest { @Diagnostic(type = ErroneousCustomExclusionMapper.class, kind = javax.tools.Diagnostic.Kind.ERROR, line = 17, - messageRegExp = "Can't map property \".*NestedSource nested\" to \".*NestedTarget nested\"\\. " + - "Consider to declare/implement a mapping method: \".*NestedTarget map\\(.*NestedSource value\\)" + - "\"\\.") + message = "Can't map property \"Source.NestedSource nested\" to \"Target.NestedTarget nested\". " + + "Consider to declare/implement a mapping method: \"Target.NestedTarget map(Source.NestedSource value)\".") } ) - @Test + @ProcessorTest public void shouldFailToCreateMappingForExcludedClass() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/custom/Source.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/custom/Source.java index 6cdc3e2752..cf5581c726 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/custom/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/exclusions/custom/Source.java @@ -39,4 +39,4 @@ public void setNested(NestedSource nested) { } // tag::documentation[] } -// tag::documentation[] +// end::documentation[] diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionary.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionary.java index bec3c420cd..e0fa0df8da 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionary.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionary.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.nestedbeans.maps; import java.util.Map; +import java.util.Objects; public class AntonymsDictionary { private Map antonyms; @@ -36,7 +37,7 @@ public boolean equals(Object o) { AntonymsDictionary antonymsDictionary = (AntonymsDictionary) o; - return antonyms != null ? antonyms.equals( antonymsDictionary.antonyms ) : antonymsDictionary.antonyms == null; + return Objects.equals( antonyms, antonymsDictionary.antonyms ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionaryDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionaryDto.java index 33ccfbc1cc..9ac7913c9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionaryDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/AntonymsDictionaryDto.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.nestedbeans.maps; import java.util.Map; +import java.util.Objects; public class AntonymsDictionaryDto { private Map antonyms; @@ -36,8 +37,7 @@ public boolean equals(Object o) { AntonymsDictionaryDto antonymsDictionaryDto = (AntonymsDictionaryDto) o; - return antonyms != null ? antonyms.equals( antonymsDictionaryDto.antonyms ) : - antonymsDictionaryDto.antonyms == null; + return Objects.equals( antonyms, antonymsDictionaryDto.antonyms ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/Word.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/Word.java index 2d25012703..cd4b03114e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/Word.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/Word.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans.maps; +import java.util.Objects; + public class Word { private String textValue; @@ -34,7 +36,7 @@ public boolean equals(Object o) { Word word = (Word) o; - return textValue != null ? textValue.equals( word.textValue ) : word.textValue == null; + return Objects.equals( textValue, word.textValue ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/WordDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/WordDto.java index 57a99fd519..d31f025d5d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/WordDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/maps/WordDto.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans.maps; +import java.util.Objects; + public class WordDto { private String textValue; @@ -34,7 +36,7 @@ public boolean equals(Object o) { WordDto wordDto = (WordDto) o; - return textValue != null ? textValue.equals( wordDto.textValue ) : wordDto.textValue == null; + return Objects.equals( textValue, wordDto.textValue ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/mixed/AutomappingAndNestedTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/mixed/AutomappingAndNestedTest.java index 3c381cfb4a..80922ae5c6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/mixed/AutomappingAndNestedTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/mixed/AutomappingAndNestedTest.java @@ -5,11 +5,7 @@ */ package org.mapstruct.ap.test.nestedbeans.mixed; -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.nestedbeans.mixed._target.FishDto; import org.mapstruct.ap.test.nestedbeans.mixed._target.FishTankDto; import org.mapstruct.ap.test.nestedbeans.mixed._target.FishTankWithNestedDocumentDto; @@ -30,10 +26,12 @@ import org.mapstruct.ap.test.nestedbeans.mixed.source.WaterQuality; import org.mapstruct.ap.test.nestedbeans.mixed.source.WaterQualityReport; 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; + /** * * @author Sjaak Derksen @@ -64,18 +62,17 @@ FishTankMapperWithDocument.class }) @IssueKey("1057") -@RunWith(AnnotationProcessorTestRunner.class) public class AutomappingAndNestedTest { - @Rule - public GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( FishTankMapper.class, FishTankMapperConstant.class, FishTankMapperExpression.class, FishTankMapperWithDocument.class ); - @Test + @ProcessorTest public void shouldAutomapAndHandleSourceAndTargetPropertyNesting() { // -- prepare @@ -117,7 +114,7 @@ public void shouldAutomapAndHandleSourceAndTargetPropertyNesting() { .isEqualTo( source.getQuality().getReport().getOrganisationName() ); } - @Test + @ProcessorTest public void shouldAutomapAndHandleSourceAndTargetPropertyNestingReverse() { // -- prepare @@ -156,7 +153,7 @@ public void shouldAutomapAndHandleSourceAndTargetPropertyNestingReverse() { .isEqualTo( source.getQuality().getReport().getVerdict() ); } - @Test + @ProcessorTest public void shouldAutomapAndHandleSourceAndTargetPropertyNestingAndConstant() { // -- prepare @@ -185,7 +182,7 @@ public void shouldAutomapAndHandleSourceAndTargetPropertyNestingAndConstant() { } - @Test + @ProcessorTest public void shouldAutomapAndHandleSourceAndTargetPropertyNestingAndExpresion() { // -- prepare @@ -210,7 +207,7 @@ public void shouldAutomapAndHandleSourceAndTargetPropertyNestingAndExpresion() { assertThat( target.getQuality().getReport().getOrganisation().getName() ).isEqualTo( "Dunno" ); } - @Test + @ProcessorTest public void shouldAutomapIntermediateLevelAndMapConstant() { // -- prepare diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/multiplecollections/Garage.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/multiplecollections/Garage.java index c2497e9787..7505aeee13 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/multiplecollections/Garage.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/multiplecollections/Garage.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.nestedbeans.multiplecollections; import java.util.List; +import java.util.Objects; import org.mapstruct.ap.test.nestedbeans.Car; @@ -48,10 +49,10 @@ public boolean equals(Object o) { Garage garage = (Garage) o; - if ( cars != null ? !cars.equals( garage.cars ) : garage.cars != null ) { + if ( !Objects.equals( cars, garage.cars ) ) { return false; } - return usedCars != null ? usedCars.equals( garage.usedCars ) : garage.usedCars == null; + return Objects.equals( usedCars, garage.usedCars ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/multiplecollections/GarageDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/multiplecollections/GarageDto.java index 190a06d280..970c558f64 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/multiplecollections/GarageDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/multiplecollections/GarageDto.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.nestedbeans.multiplecollections; import java.util.List; +import java.util.Objects; import org.mapstruct.ap.test.nestedbeans.CarDto; @@ -49,10 +50,10 @@ public boolean equals(Object o) { GarageDto garageDto = (GarageDto) o; - if ( cars != null ? !cars.equals( garageDto.cars ) : garageDto.cars != null ) { + if ( !Objects.equals( cars, garageDto.cars ) ) { return false; } - return usedCars != null ? usedCars.equals( garageDto.usedCars ) : garageDto.usedCars == null; + return Objects.equals( usedCars, garageDto.usedCars ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/CarDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/CarDto.java index 682eac481b..a7b27970ce 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/CarDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/CarDto.java @@ -6,6 +6,7 @@ package org.mapstruct.ap.test.nestedbeans.other; import java.util.List; +import java.util.Objects; public class CarDto { @@ -60,10 +61,10 @@ public boolean equals(Object o) { if ( year != carDto.year ) { return false; } - if ( name != null ? !name.equals( carDto.name ) : carDto.name != null ) { + if ( !Objects.equals( name, carDto.name ) ) { return false; } - return wheels != null ? wheels.equals( carDto.wheels ) : carDto.wheels == null; + return Objects.equals( wheels, carDto.wheels ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/HouseDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/HouseDto.java index 29ed80a8a2..d7b0631f41 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/HouseDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/HouseDto.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans.other; +import java.util.Objects; + public class HouseDto { private String name; @@ -58,10 +60,10 @@ public boolean equals(Object o) { if ( year != houseDto.year ) { return false; } - if ( name != null ? !name.equals( houseDto.name ) : houseDto.name != null ) { + if ( !Objects.equals( name, houseDto.name ) ) { return false; } - return roof != null ? roof.equals( houseDto.roof ) : houseDto.roof == null; + return Objects.equals( roof, houseDto.roof ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/RoofDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/RoofDto.java index 5382c09a5c..05b984b89d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/RoofDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/RoofDto.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans.other; +import java.util.Objects; + public class RoofDto { private String color; @@ -34,7 +36,7 @@ public boolean equals(Object o) { RoofDto roofDto = (RoofDto) o; - return color != null ? color.equals( roofDto.color ) : roofDto.color == null; + return Objects.equals( color, roofDto.color ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/UserDto.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/UserDto.java index df614fc1ca..eb4529a212 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/UserDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/other/UserDto.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.nestedbeans.other; +import java.util.Objects; + public class UserDto { private String name; @@ -55,13 +57,13 @@ public boolean equals(Object o) { UserDto userDto = (UserDto) o; - if ( name != null ? !name.equals( userDto.name ) : userDto.name != null ) { + if ( !Objects.equals( name, userDto.name ) ) { return false; } - if ( car != null ? !car.equals( userDto.car ) : userDto.car != null ) { + if ( !Objects.equals( car, userDto.car ) ) { return false; } - return house != null ? house.equals( userDto.house ) : userDto.house == null; + return Objects.equals( house, userDto.house ); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableCollectionElementPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableCollectionElementPropertyMapper.java deleted file mode 100644 index 493442cae0..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableCollectionElementPropertyMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.erroneous; - -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseCollectionElementPropertyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR) -public abstract class UnmappableCollectionElementPropertyMapper extends BaseCollectionElementPropertyMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepListMapper.java deleted file mode 100644 index 275d043cfb..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepListMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.erroneous; - -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepListMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR) -public abstract class UnmappableDeepListMapper extends BaseDeepListMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepMapKeyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepMapKeyMapper.java deleted file mode 100644 index c26fbb7df6..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepMapKeyMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.erroneous; - -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapKeyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR) -public abstract class UnmappableDeepMapKeyMapper extends BaseDeepMapKeyMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepMapValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepMapValueMapper.java deleted file mode 100644 index 4c9c8d9f93..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepMapValueMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.erroneous; - -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapValueMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR) -public abstract class UnmappableDeepMapValueMapper extends BaseDeepMapValueMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepNestingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepNestingMapper.java deleted file mode 100644 index 00b0401ee5..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableDeepNestingMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.erroneous; - -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepNestingMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR) -public abstract class UnmappableDeepNestingMapper extends BaseDeepNestingMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableEnumMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableEnumMapper.java deleted file mode 100644 index 1733c9af0b..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableEnumMapper.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.erroneous; - -import org.mapstruct.Mapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.Car; -import org.mapstruct.ap.test.nestedbeans.unmappable.CarDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.Cat; -import org.mapstruct.ap.test.nestedbeans.unmappable.CatDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.Color; -import org.mapstruct.ap.test.nestedbeans.unmappable.ColorDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.Computer; -import org.mapstruct.ap.test.nestedbeans.unmappable.ComputerDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.Dictionary; -import org.mapstruct.ap.test.nestedbeans.unmappable.DictionaryDto; -import org.mapstruct.ap.test.nestedbeans.unmappable.User; -import org.mapstruct.ap.test.nestedbeans.unmappable.UserDto; - -@Mapper -public abstract class UnmappableEnumMapper { - - abstract UserDto userToUserDto(User user); - - public ColorDto map(Color color) { - return new ColorDto(); - } - - public CarDto map(Car carDto) { - return new CarDto(); - } - - public DictionaryDto map(Dictionary dictionary) { - return new DictionaryDto(); - } - - public ComputerDto map(Computer computer) { - return new ComputerDto(); - } - - public CatDto map(Cat cat) { - return new CatDto(); - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceCollectionElementPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceCollectionElementPropertyMapper.java new file mode 100644 index 0000000000..4696dbca7c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceCollectionElementPropertyMapper.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.nestedbeans.unmappable.erroneous; + +import static org.mapstruct.ReportingPolicy.ERROR; +import static org.mapstruct.ReportingPolicy.IGNORE; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseCollectionElementPropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = ERROR ) +public abstract class UnmappableSourceCollectionElementPropertyMapper extends BaseCollectionElementPropertyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepListMapper.java new file mode 100644 index 0000000000..95485fada2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepListMapper.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.nestedbeans.unmappable.erroneous; + +import static org.mapstruct.ReportingPolicy.ERROR; +import static org.mapstruct.ReportingPolicy.IGNORE; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepListMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = ERROR ) +public abstract class UnmappableSourceDeepListMapper extends BaseDeepListMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapKeyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapKeyMapper.java new file mode 100644 index 0000000000..2cb9c30dc8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapKeyMapper.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.nestedbeans.unmappable.erroneous; + +import static org.mapstruct.ReportingPolicy.ERROR; +import static org.mapstruct.ReportingPolicy.IGNORE; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapKeyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = ERROR ) +public abstract class UnmappableSourceDeepMapKeyMapper extends BaseDeepMapKeyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapValueMapper.java new file mode 100644 index 0000000000..1cc2df3d41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepMapValueMapper.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.nestedbeans.unmappable.erroneous; + +import static org.mapstruct.ReportingPolicy.ERROR; +import static org.mapstruct.ReportingPolicy.IGNORE; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapValueMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = ERROR ) +public abstract class UnmappableSourceDeepMapValueMapper extends BaseDeepMapValueMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepNestingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepNestingMapper.java new file mode 100644 index 0000000000..d166610445 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceDeepNestingMapper.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.nestedbeans.unmappable.erroneous; + +import static org.mapstruct.ReportingPolicy.ERROR; +import static org.mapstruct.ReportingPolicy.IGNORE; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepNestingMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = ERROR ) +public abstract class UnmappableSourceDeepNestingMapper extends BaseDeepNestingMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceEnumMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceEnumMapper.java new file mode 100644 index 0000000000..8fcb737ee4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceEnumMapper.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.nestedbeans.unmappable.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.Car; +import org.mapstruct.ap.test.nestedbeans.unmappable.CarDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Cat; +import org.mapstruct.ap.test.nestedbeans.unmappable.CatDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Color; +import org.mapstruct.ap.test.nestedbeans.unmappable.ColorDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Computer; +import org.mapstruct.ap.test.nestedbeans.unmappable.ComputerDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.Dictionary; +import org.mapstruct.ap.test.nestedbeans.unmappable.DictionaryDto; +import org.mapstruct.ap.test.nestedbeans.unmappable.User; +import org.mapstruct.ap.test.nestedbeans.unmappable.UserDto; + +@Mapper +public abstract class UnmappableSourceEnumMapper { + + abstract UserDto userToUserDto(User user); + + public ColorDto map(Color color) { + return new ColorDto(); + } + + public CarDto map(Car carDto) { + return new CarDto(); + } + + public DictionaryDto map(Dictionary dictionary) { + return new DictionaryDto(); + } + + public ComputerDto map(Computer computer) { + return new ComputerDto(); + } + + public CatDto map(Cat cat) { + return new CatDto(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceValuePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceValuePropertyMapper.java new file mode 100644 index 0000000000..25be366250 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableSourceValuePropertyMapper.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.nestedbeans.unmappable.erroneous; + +import static org.mapstruct.ReportingPolicy.ERROR; +import static org.mapstruct.ReportingPolicy.IGNORE; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseValuePropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = ERROR ) +public abstract class UnmappableSourceValuePropertyMapper extends BaseValuePropertyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetCollectionElementPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetCollectionElementPropertyMapper.java new file mode 100644 index 0000000000..5198324cbe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetCollectionElementPropertyMapper.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.nestedbeans.unmappable.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseCollectionElementPropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR ) +public abstract class UnmappableTargetCollectionElementPropertyMapper extends BaseCollectionElementPropertyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepListMapper.java new file mode 100644 index 0000000000..993e30ec60 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepListMapper.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.nestedbeans.unmappable.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepListMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR ) +public abstract class UnmappableTargetDeepListMapper extends BaseDeepListMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepMapKeyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepMapKeyMapper.java new file mode 100644 index 0000000000..e30533d00d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepMapKeyMapper.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.nestedbeans.unmappable.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapKeyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR ) +public abstract class UnmappableTargetDeepMapKeyMapper extends BaseDeepMapKeyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepMapValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepMapValueMapper.java new file mode 100644 index 0000000000..f46651dbc1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepMapValueMapper.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.nestedbeans.unmappable.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapValueMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR ) +public abstract class UnmappableTargetDeepMapValueMapper extends BaseDeepMapValueMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepNestingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepNestingMapper.java new file mode 100644 index 0000000000..736aef3d84 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetDeepNestingMapper.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.nestedbeans.unmappable.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepNestingMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR ) +public abstract class UnmappableTargetDeepNestingMapper extends BaseDeepNestingMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetValuePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetValuePropertyMapper.java new file mode 100644 index 0000000000..e43f7d2e1a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableTargetValuePropertyMapper.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.nestedbeans.unmappable.erroneous; + +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseValuePropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR ) +public abstract class UnmappableTargetValuePropertyMapper extends BaseValuePropertyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableValuePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableValuePropertyMapper.java deleted file mode 100644 index dd657c8569..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/erroneous/UnmappableValuePropertyMapper.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.erroneous; - -import org.mapstruct.Mapper; -import org.mapstruct.ReportingPolicy; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseValuePropertyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class, unmappedTargetPolicy = ReportingPolicy.ERROR) -public abstract class UnmappableValuePropertyMapper extends BaseValuePropertyMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnCollectionElementPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnCollectionElementPropertyMapper.java new file mode 100644 index 0000000000..23d12b18d2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnCollectionElementPropertyMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseCollectionElementPropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = WARN ) +public abstract class UnmappableSourceWarnCollectionElementPropertyMapper extends BaseCollectionElementPropertyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepListMapper.java new file mode 100644 index 0000000000..1c72e38ff0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepListMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepListMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = WARN ) +public abstract class UnmappableSourceWarnDeepListMapper extends BaseDeepListMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepMapKeyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepMapKeyMapper.java new file mode 100644 index 0000000000..bc4f5daa46 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepMapKeyMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapKeyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = WARN ) +public abstract class UnmappableSourceWarnDeepMapKeyMapper extends BaseDeepMapKeyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepMapValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepMapValueMapper.java new file mode 100644 index 0000000000..08dc43edec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepMapValueMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapValueMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = WARN ) +public abstract class UnmappableSourceWarnDeepMapValueMapper extends BaseDeepMapValueMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepNestingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepNestingMapper.java new file mode 100644 index 0000000000..61cbc3900b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnDeepNestingMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepNestingMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = WARN ) +public abstract class UnmappableSourceWarnDeepNestingMapper extends BaseDeepNestingMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnValuePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnValuePropertyMapper.java new file mode 100644 index 0000000000..fd4f81c97d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableSourceWarnValuePropertyMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseValuePropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = IGNORE, unmappedSourcePolicy = WARN ) +public abstract class UnmappableSourceWarnValuePropertyMapper extends BaseValuePropertyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnCollectionElementPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnCollectionElementPropertyMapper.java new file mode 100644 index 0000000000..e656dac371 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnCollectionElementPropertyMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseCollectionElementPropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = WARN, unmappedSourcePolicy = IGNORE ) +public abstract class UnmappableTargetWarnCollectionElementPropertyMapper extends BaseCollectionElementPropertyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepListMapper.java new file mode 100644 index 0000000000..abf4d4ac78 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepListMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepListMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = WARN, unmappedSourcePolicy = IGNORE ) +public abstract class UnmappableTargetWarnDeepListMapper extends BaseDeepListMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapKeyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapKeyMapper.java new file mode 100644 index 0000000000..bb5370dd7f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapKeyMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapKeyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = WARN, unmappedSourcePolicy = IGNORE ) +public abstract class UnmappableTargetWarnDeepMapKeyMapper extends BaseDeepMapKeyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapValueMapper.java new file mode 100644 index 0000000000..b202adcde7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepMapValueMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapValueMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = WARN, unmappedSourcePolicy = IGNORE ) +public abstract class UnmappableTargetWarnDeepMapValueMapper extends BaseDeepMapValueMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepNestingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepNestingMapper.java new file mode 100644 index 0000000000..99ff003c1a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnDeepNestingMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepNestingMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = WARN, unmappedSourcePolicy = IGNORE ) +public abstract class UnmappableTargetWarnDeepNestingMapper extends BaseDeepNestingMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnValuePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnValuePropertyMapper.java new file mode 100644 index 0000000000..a339768492 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableTargetWarnValuePropertyMapper.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.nestedbeans.unmappable.warn; + +import static org.mapstruct.ReportingPolicy.IGNORE; +import static org.mapstruct.ReportingPolicy.WARN; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.BaseValuePropertyMapper; +import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; + +@Mapper( uses = RoofTypeMapper.class, unmappedTargetPolicy = WARN, unmappedSourcePolicy = IGNORE ) +public abstract class UnmappableTargetWarnValuePropertyMapper extends BaseValuePropertyMapper { +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnCollectionElementPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnCollectionElementPropertyMapper.java deleted file mode 100644 index 64610a61f8..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnCollectionElementPropertyMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.warn; - -import org.mapstruct.Mapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseCollectionElementPropertyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class) -public abstract class UnmappableWarnCollectionElementPropertyMapper extends BaseCollectionElementPropertyMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepListMapper.java deleted file mode 100644 index 6ca1ce71da..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepListMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.warn; - -import org.mapstruct.Mapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepListMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class) -public abstract class UnmappableWarnDeepListMapper extends BaseDeepListMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepMapKeyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepMapKeyMapper.java deleted file mode 100644 index a010ac05ed..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepMapKeyMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.warn; - -import org.mapstruct.Mapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapKeyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class) -public abstract class UnmappableWarnDeepMapKeyMapper extends BaseDeepMapKeyMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepMapValueMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepMapValueMapper.java deleted file mode 100644 index 53e60c6687..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepMapValueMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.warn; - -import org.mapstruct.Mapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepMapValueMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class) -public abstract class UnmappableWarnDeepMapValueMapper extends BaseDeepMapValueMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepNestingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepNestingMapper.java deleted file mode 100644 index 50d0b366b0..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnDeepNestingMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.warn; - -import org.mapstruct.Mapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseDeepNestingMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class) -public abstract class UnmappableWarnDeepNestingMapper extends BaseDeepNestingMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnValuePropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnValuePropertyMapper.java deleted file mode 100644 index f2877b215b..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedbeans/unmappable/warn/UnmappableWarnValuePropertyMapper.java +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.test.nestedbeans.unmappable.warn; - -import org.mapstruct.Mapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.BaseValuePropertyMapper; -import org.mapstruct.ap.test.nestedbeans.unmappable.RoofTypeMapper; - -@Mapper(uses = RoofTypeMapper.class) -public abstract class UnmappableWarnValuePropertyMapper extends BaseValuePropertyMapper { -} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java index f16732beb5..03c47f7a49 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/NestedMappingMethodInvocationTest.java @@ -5,13 +5,10 @@ */ package org.mapstruct.ap.test.nestedmethodcall; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.ArrayList; +import java.util.Calendar; import java.util.GregorianCalendar; import java.util.List; -import java.util.Locale; - import javax.xml.bind.JAXBElement; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeConstants; @@ -19,12 +16,14 @@ import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; -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 org.mapstruct.ap.testutil.WithJavaxJaxb; + +import static org.assertj.core.api.Assertions.assertThat; /** * Test for the nested invocation of mapping methods. @@ -32,17 +31,13 @@ * @author Sjaak Derksen */ @IssueKey("134") -@RunWith(AnnotationProcessorTestRunner.class) +@DefaultLocale("de") +@ReadsDefaultTimeZone public class NestedMappingMethodInvocationTest { public static final QName QNAME = new QName( "dont-care" ); - @Before - public void setDefaultLocale() { - Locale.setDefault( Locale.GERMAN ); - } - - @Test + @ProcessorTest @WithClasses( { OrderTypeToOrderDtoMapper.class, OrderDto.class, @@ -50,6 +45,7 @@ public void setDefaultLocale() { OrderDetailsType.class, OrderType.class } ) + @WithJavaxJaxb public void shouldMapViaMethodAndMethod() throws DatatypeConfigurationException { OrderTypeToOrderDtoMapper instance = OrderTypeToOrderDtoMapper.INSTANCE; OrderDto target = instance.sourceToTarget( createOrderType() ); @@ -63,30 +59,32 @@ public void shouldMapViaMethodAndMethod() throws DatatypeConfigurationException assertThat( target.getOrderDetails().getDescription() ).containsExactly( "elem1", "elem2" ); } - @Test + @ProcessorTest @WithClasses( { SourceTypeTargetDtoMapper.class, SourceType.class, ObjectFactory.class, TargetDto.class } ) - public void shouldMapViaMethodAndConversion() throws DatatypeConfigurationException { + @WithJavaxJaxb + public void shouldMapViaMethodAndConversion() { SourceTypeTargetDtoMapper instance = SourceTypeTargetDtoMapper.INSTANCE; TargetDto target = instance.sourceToTarget( createSource() ); assertThat( target ).isNotNull(); - assertThat( target.getDate() ).isEqualTo( new GregorianCalendar( 2013, 6, 6 ).getTime() ); + assertThat( target.getDate() ).isEqualTo( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); } - @Test + @ProcessorTest @WithClasses( { SourceTypeTargetDtoMapper.class, SourceType.class, ObjectFactory.class, TargetDto.class } ) - public void shouldMapViaConversionAndMethod() throws DatatypeConfigurationException { + @WithJavaxJaxb + public void shouldMapViaConversionAndMethod() { SourceTypeTargetDtoMapper instance = SourceTypeTargetDtoMapper.INSTANCE; SourceType source = instance.targetToSource( createTarget() ); @@ -97,36 +95,36 @@ public void shouldMapViaConversionAndMethod() throws DatatypeConfigurationExcept } private OrderType createOrderType() throws DatatypeConfigurationException { - List> dates = new ArrayList>(); + List> dates = new ArrayList<>(); dates.add( - new JAXBElement( - QNAME, - XMLGregorianCalendar.class, - createXmlCal( 1999, 3, 2 ) - ) + new JAXBElement<>( + QNAME, + XMLGregorianCalendar.class, + createXmlCal( 1999, 3, 2 ) + ) ); dates.add( - new JAXBElement( - QNAME, - XMLGregorianCalendar.class, - createXmlCal( 2004, 7, 28 ) - ) + new JAXBElement<>( + QNAME, + XMLGregorianCalendar.class, + createXmlCal( 2004, 7, 28 ) + ) ); - List> description = new ArrayList>(); - description.add( new JAXBElement( QNAME, String.class, "elem1" ) ); - description.add( new JAXBElement( QNAME, String.class, "elem2" ) ); + List> description = new ArrayList<>(); + description.add( new JAXBElement<>( QNAME, String.class, "elem1" ) ); + description.add( new JAXBElement<>( QNAME, String.class, "elem2" ) ); OrderType orderType = new OrderType(); - orderType.setOrderNumber( new JAXBElement( QNAME, Long.class, 5L ) ); + orderType.setOrderNumber( new JAXBElement<>( QNAME, Long.class, 5L ) ); orderType.setOrderDetails( - new JAXBElement( - QNAME, - OrderDetailsType.class, - new OrderDetailsType() - ) + new JAXBElement<>( + QNAME, + OrderDetailsType.class, + new OrderDetailsType() + ) ); - orderType.getOrderDetails().getValue().setName( new JAXBElement( QNAME, String.class, "test" ) ); + orderType.getOrderDetails().getValue().setName( new JAXBElement<>( QNAME, String.class, "test" ) ); orderType.getOrderDetails().getValue().setDescription( description ); orderType.setDates( dates ); @@ -141,13 +139,13 @@ private XMLGregorianCalendar createXmlCal( int year, int month, int day ) private SourceType createSource() { SourceType source = new SourceType(); - source.setDate( new JAXBElement( QNAME, String.class, "06.07.2013" ) ); + source.setDate( new JAXBElement<>( QNAME, String.class, "06.07.2013" ) ); return source; } private TargetDto createTarget() { TargetDto target = new TargetDto(); - target.setDate( new GregorianCalendar( 2013, 6, 6 ).getTime() ); + target.setDate( new GregorianCalendar( 2013, Calendar.JULY, 6 ).getTime() ); return target; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/ObjectFactory.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/ObjectFactory.java index 05fd3e7c63..3d2c454255 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/ObjectFactory.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/ObjectFactory.java @@ -15,6 +15,6 @@ public class ObjectFactory { public JAXBElement createDate(String date) { - return new JAXBElement( new QName( "dont-care" ), String.class, "06.07.2013" ); + return new JAXBElement<>( new QName( "dont-care" ), String.class, "06.07.2013" ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTypeTargetDtoMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTypeTargetDtoMapper.java index 87371f004f..895d3dc922 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTypeTargetDtoMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedmethodcall/SourceTypeTargetDtoMapper.java @@ -17,7 +17,7 @@ public interface SourceTypeTargetDtoMapper { SourceTypeTargetDtoMapper INSTANCE = Mappers.getMapper( SourceTypeTargetDtoMapper.class ); - @Mapping(source = "date", target = "date", dateFormat = "dd.MM.yyyy") + @Mapping(target = "date", source = "date", dateFormat = "dd.MM.yyyy") TargetDto sourceToTarget(SourceType source); SourceType targetToSource( TargetDto source ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedproperties/simple/SimpleNestedPropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedproperties/simple/SimpleNestedPropertiesTest.java index 98faca0cca..ae5833794e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedproperties/simple/SimpleNestedPropertiesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedproperties/simple/SimpleNestedPropertiesTest.java @@ -5,38 +5,36 @@ */ package org.mapstruct.ap.test.nestedproperties.simple; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.nestedproperties.simple._target.TargetObject; import org.mapstruct.ap.test.nestedproperties.simple.source.SourceProps; import org.mapstruct.ap.test.nestedproperties.simple.source.SourceRoot; 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; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Sebastian Hasait */ @WithClasses({ SourceRoot.class, SourceProps.class, TargetObject.class }) @IssueKey("407") -@RunWith(AnnotationProcessorTestRunner.class) public class SimpleNestedPropertiesTest { - @Test + @ProcessorTest @WithClasses({ SimpleMapper.class }) public void testNull() { TargetObject targetObject = SimpleMapper.MAPPER.toTargetObject( null ); - assertNull( targetObject ); + assertThat( targetObject ).isNull(); } - @Test + @ProcessorTest @WithClasses({ SimpleMapper.class }) public void testViaNull() { SourceRoot sourceRoot = new SourceRoot(); @@ -57,7 +55,7 @@ public void testViaNull() { assertNull( targetObject.getStringValue() ); } - @Test + @ProcessorTest @WithClasses({ SimpleMapper.class }) public void testFilled() { SourceRoot sourceRoot = new SourceRoot(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsource/exceptions/NestedExceptionTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsource/exceptions/NestedExceptionTest.java index 09f47ccebb..97206784aa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsource/exceptions/NestedExceptionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsource/exceptions/NestedExceptionTest.java @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.nestedsource.exceptions; -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; /** * @@ -24,10 +22,9 @@ ResourceMapper.class }) @IssueKey("1332") -@RunWith(AnnotationProcessorTestRunner.class) public class NestedExceptionTest { - @Test + @ProcessorTest public void shouldGenerateCodeThatCompiles() { } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsource/exceptions/ResourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsource/exceptions/ResourceMapper.java index b1dcd8cd04..712d160908 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsource/exceptions/ResourceMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsource/exceptions/ResourceMapper.java @@ -15,7 +15,7 @@ @Mapper public interface ResourceMapper { - @Mapping(source = "bucket.user.uuid", target = "userId") + @Mapping(target = "userId", source = "bucket.user.uuid") ResourceDto map(Resource r) throws NoSuchUser; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsource/parameter/NormalizingTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsource/parameter/NormalizingTest.java index 696d983dc6..de9a821527 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsource/parameter/NormalizingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsource/parameter/NormalizingTest.java @@ -5,23 +5,20 @@ */ package org.mapstruct.ap.test.nestedsource.parameter; -import static org.assertj.core.api.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; /** * @author Sjaak Derksen */ @WithClasses({ FontDto.class, LetterDto.class, LetterEntity.class, LetterMapper.class }) @IssueKey("836") -@RunWith(AnnotationProcessorTestRunner.class) public class NormalizingTest { - @Test + @ProcessorTest public void shouldGenerateImplementationForPropertyNamesOnly() { FontDto fontIn = new FontDto(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryErroneous.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryErroneous.java index 5ed5ee57d0..5f8be28763 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryErroneous.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ArtistToChartEntryErroneous.java @@ -28,9 +28,22 @@ public interface ArtistToChartEntryErroneous { @Mapping(target = "city", ignore = true), @Mapping(target = "position", source = "position") } ) - ChartEntry forward(Integer position); + ChartEntry forward(ChartPosition position); @InheritInverseConfiguration - Integer reverse(ChartEntry position); + ChartPosition reverse(ChartEntry position); + + class ChartPosition { + + private final int position; + + private ChartPosition(int position) { + this.position = position; + } + + public int getPosition() { + return position; + } + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java index 6fab4df190..2a0a80cbda 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/NestedSourcePropertiesTest.java @@ -5,15 +5,9 @@ */ package org.mapstruct.ap.test.nestedsourceproperties; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - import java.util.Arrays; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.nestedsourceproperties._target.AdderUsageObserver; import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; import org.mapstruct.ap.test.nestedsourceproperties._target.ChartPositions; @@ -23,25 +17,26 @@ import org.mapstruct.ap.test.nestedsourceproperties.source.Song; import org.mapstruct.ap.test.nestedsourceproperties.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.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; + /** * @author Sjaak Derksen */ @WithClasses({ Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class }) @IssueKey("65") -@RunWith(AnnotationProcessorTestRunner.class) public class NestedSourcePropertiesTest { - @Rule - public final GeneratedSource generatedSource = new GeneratedSource(); + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntry.class }) public void shouldGenerateImplementationForPropertyNamesOnly() { generatedSource.addComparisonToFixtureFor( ArtistToChartEntry.class ); @@ -73,7 +68,7 @@ public void shouldGenerateImplementationForPropertyNamesOnly() { assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntry.class }) public void shouldGenerateImplementationForMultipleParam() { @@ -108,7 +103,7 @@ public void shouldGenerateImplementationForMultipleParam() { assertThat( chartEntry.getSongTitle() ).isEqualTo( "A Hard Day's Night" ); } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntry.class }) public void shouldPickPropertyNameOverParameterName() { @@ -127,7 +122,7 @@ public void shouldPickPropertyNameOverParameterName() { assertThat( chartEntry.getSongTitle() ).isNull(); } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntryAdder.class, ChartPositions.class, AdderUsageObserver.class }) public void shouldUseAddAsTargetAccessor() { @@ -142,10 +137,10 @@ public void shouldUseAddAsTargetAccessor() { assertThat( positions ).isNotNull(); assertThat( positions.getPositions() ).containsExactly( 3L, 5L ); - assertTrue( AdderUsageObserver.isUsed() ); + assertThat( AdderUsageObserver.isUsed() ).isTrue(); } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntryGetter.class, ChartPositions.class, AdderUsageObserver.class }) public void shouldUseGetAsTargetAccessor() { @@ -160,21 +155,21 @@ public void shouldUseGetAsTargetAccessor() { assertThat( positions ).isNotNull(); assertThat( positions.getPositions() ).containsExactly( 3L, 5L ); - assertFalse( AdderUsageObserver.isUsed() ); + assertThat( AdderUsageObserver.isUsed() ).isFalse(); } - @Test - @IssueKey( "838" ) + @ProcessorTest + @IssueKey("838") @ExpectedCompilationOutcome( - value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic( type = ArtistToChartEntryErroneous.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 34, - messageRegExp = "java.lang.Integer does not have an accessible parameterless constructor." ) - } + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ArtistToChartEntryErroneous.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 34, + message = "ArtistToChartEntryErroneous.ChartPosition does not have an accessible constructor.") + } ) @WithClasses({ ArtistToChartEntryErroneous.class }) - public void reverseShouldRaiseErrorForEmptyContructor() { + public void inverseShouldRaiseErrorForNotAccessibleConstructor() { } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ReversingNestedSourcePropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ReversingNestedSourcePropertiesTest.java index db27970b51..2010a205de 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ReversingNestedSourcePropertiesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/ReversingNestedSourcePropertiesTest.java @@ -5,11 +5,6 @@ */ package org.mapstruct.ap.test.nestedsourceproperties; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.Test; -import org.junit.runner.RunWith; import org.mapstruct.ap.test.nestedsourceproperties._target.BaseChartEntry; import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntryComposed; @@ -23,18 +18,19 @@ import org.mapstruct.ap.test.nestedsourceproperties.source.SourceDtoFactory; import org.mapstruct.ap.test.nestedsourceproperties.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.AnnotationProcessorTestRunner; + +import static org.assertj.core.api.Assertions.assertThat; /** * @author Sjaak Derksen */ @IssueKey("389") @WithClasses({ Song.class, Artist.class, Chart.class, Label.class, Studio.class, ChartEntry.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class ReversingNestedSourcePropertiesTest { - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntryReverse.class }) public void shouldGenerateNestedReverse() { @@ -63,7 +59,7 @@ public void shouldGenerateNestedReverse() { assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntryWithIgnoresReverse.class }) public void shouldIgnoreEverytingBelowArtist() { @@ -86,7 +82,7 @@ public void shouldIgnoreEverytingBelowArtist() { assertThat( song2.getArtist() ).isNull(); } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntryUpdateReverse.class }) public void shouldGenerateNestedUpdateReverse() { @@ -116,7 +112,7 @@ public void shouldGenerateNestedUpdateReverse() { assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); } - @Test + @ProcessorTest @WithClasses( { ArtistToChartEntryWithFactoryReverse.class, SourceDtoFactory.class } ) public void shouldGenerateNestedReverseWithFactory() { @@ -151,7 +147,7 @@ public void shouldGenerateNestedReverseWithFactory() { } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntryComposedReverse.class, ChartEntryComposed.class, ChartEntryLabel.class }) public void shouldGenerateNestedComposedReverse() { @@ -181,7 +177,7 @@ public void shouldGenerateNestedComposedReverse() { assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntryWithMappingReverse.class, ChartEntryWithMapping.class }) public void shouldGenerateNestedWithMappingReverse() { @@ -210,7 +206,7 @@ public void shouldGenerateNestedWithMappingReverse() { assertThat( song2.getArtist().getLabel().getStudio().getName() ).isEqualTo( "Abbey Road" ); } - @Test + @ProcessorTest @WithClasses({ ArtistToChartEntryWithConfigReverse.class, ArtistToChartEntryConfig.class, diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/AdderUsageObserver.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/AdderUsageObserver.java index 96de5cd1d0..e7cae29460 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/AdderUsageObserver.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/AdderUsageObserver.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.nestedsourceproperties._target; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryLabel.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryLabel.java index bd13f1fcb1..77d2e68655 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryLabel.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartEntryLabel.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.nestedsourceproperties._target; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartPositions.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartPositions.java index 936c45dea5..f91534fe99 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartPositions.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/_target/ChartPositions.java @@ -13,7 +13,7 @@ */ public class ChartPositions { - private final List positions = new ArrayList(); + private final List positions = new ArrayList<>(); public List getPositions() { return positions; diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/source/Song.java b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/source/Song.java index f3c2856260..f1ebe50745 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/source/Song.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedsourceproperties/source/Song.java @@ -7,7 +7,6 @@ import java.util.List; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java index 515679a0da..db8d635d62 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtist.java @@ -52,10 +52,10 @@ public abstract class ChartEntryToArtist { protected List mapPosition(Integer in) { if ( in != null ) { - return new ArrayList( Arrays.asList( in ) ); + return new ArrayList<>( Arrays.asList( in ) ); } else { - return new ArrayList(); + return new ArrayList<>(); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdate.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdate.java index a65bfde8db..eb9191daf3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdate.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/ChartEntryToArtistUpdate.java @@ -46,7 +46,7 @@ protected List mapPosition(Integer in) { return Arrays.asList( in ); } else { - return Collections.emptyList(); + return Collections.emptyList(); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedProductPropertiesTest.java b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedProductPropertiesTest.java index 44a73abd4b..b10c51ab39 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedProductPropertiesTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nestedtargetproperties/NestedProductPropertiesTest.java @@ -5,9 +5,7 @@ */ package org.mapstruct.ap.test.nestedtargetproperties; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; +import org.junit.jupiter.api.extension.RegisterExtension; import org.mapstruct.ap.test.nestedsourceproperties._target.ChartEntry; import org.mapstruct.ap.test.nestedsourceproperties.source.Artist; import org.mapstruct.ap.test.nestedsourceproperties.source.Chart; @@ -15,8 +13,8 @@ import org.mapstruct.ap.test.nestedsourceproperties.source.Song; import org.mapstruct.ap.test.nestedsourceproperties.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.AnnotationProcessorTestRunner; import org.mapstruct.ap.testutil.runner.GeneratedSource; import static org.assertj.core.api.Assertions.assertThat; @@ -36,16 +34,15 @@ ChartEntryToArtistUpdate.class } ) @IssueKey("389") -@RunWith(AnnotationProcessorTestRunner.class) public class NestedProductPropertiesTest { - @Rule - public GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource().addComparisonToFixtureFor( ChartEntryToArtist.class, ChartEntryToArtistUpdate.class ); - @Test + @ProcessorTest public void shouldMapNestedTarget() { ChartEntry chartEntry = new ChartEntry(); @@ -74,7 +71,7 @@ public void shouldMapNestedTarget() { } - @Test + @ProcessorTest public void shouldMapNestedComposedTarget() { ChartEntry chartEntry1 = new ChartEntry(); @@ -105,7 +102,7 @@ public void shouldMapNestedComposedTarget() { } - @Test + @ProcessorTest public void shouldReverseNestedTarget() { ChartEntry chartEntry = new ChartEntry(); @@ -128,7 +125,7 @@ public void shouldReverseNestedTarget() { assertThat( result.getSongTitle() ).isEqualTo( "Purple Rain" ); } - @Test + @ProcessorTest public void shouldMapNestedTargetWitUpdate() { ChartEntry chartEntry = new ChartEntry(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nonvoidsetter/NonVoidSettersTest.java b/processor/src/test/java/org/mapstruct/ap/test/nonvoidsetter/NonVoidSettersTest.java index 6745f52c02..883464ebfe 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nonvoidsetter/NonVoidSettersTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nonvoidsetter/NonVoidSettersTest.java @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.nonvoidsetter; -import static org.assertj.core.api.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; /** * Test for using non-void setters (fluent style) in the target. @@ -19,10 +17,9 @@ * @author Gunnar Morling */ @WithClasses({ Actor.class, ActorDto.class, ActorMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class NonVoidSettersTest { - @Test + @ProcessorTest @IssueKey("353") public void shouldMapAttributeWithoutSetterInSourceType() { ActorDto target = ActorMapper.INSTANCE.actorToActorDto( new Actor( 3, "Hickory Black" ) ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/MyLongWrapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/MyLongWrapper.java index d070c64941..2836f7b3dc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/MyLongWrapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/MyLongWrapper.java @@ -5,7 +5,6 @@ */ package org.mapstruct.ap.test.nullcheck; - /** * @author Sjaak Derksen */ diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/NullCheckTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/NullCheckTest.java index 290a34f733..816db7a1dc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/NullCheckTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/NullCheckTest.java @@ -5,13 +5,12 @@ */ package org.mapstruct.ap.test.nullcheck; -import static org.assertj.core.api.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; +import static org.assertj.core.api.Assertions.assertThatThrownBy; /** * Test for correct handling of null checks. @@ -28,10 +27,9 @@ Source.class, Target.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class NullCheckTest { - @Test(expected = NullPointerException.class) + @ProcessorTest @IssueKey("214") public void shouldThrowNullptrWhenCustomMapperIsInvoked() { @@ -40,10 +38,11 @@ public void shouldThrowNullptrWhenCustomMapperIsInvoked() { source.setSomeInteger( 7 ); source.setSomeLong( 2L ); - SourceTargetMapper.INSTANCE.sourceToTarget( source ); + assertThatThrownBy( () -> SourceTargetMapper.INSTANCE.sourceToTarget( source ) ) + .isInstanceOf( NullPointerException.class ); } - @Test + @ProcessorTest @IssueKey("214") public void shouldSurroundTypeConversionWithNullCheck() { @@ -58,7 +57,7 @@ public void shouldSurroundTypeConversionWithNullCheck() { } - @Test + @ProcessorTest @IssueKey("214") public void shouldSurroundArrayListConstructionWithNullCheck() { @@ -72,7 +71,7 @@ public void shouldSurroundArrayListConstructionWithNullCheck() { assertThat( target.getSomeList() ).isNull(); } - @Test + @ProcessorTest @IssueKey("237") public void shouldSurroundConversionPassedToMappingMethodWithNullCheck() { @@ -86,7 +85,7 @@ public void shouldSurroundConversionPassedToMappingMethodWithNullCheck() { assertThat( target.getSomeInteger() ).isNull(); } - @Test + @ProcessorTest @IssueKey("231") public void shouldSurroundConversionFromWrappedPassedToMappingMethodWithPrimitiveArgWithNullCheck() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/strategy/NullValueCheckTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/strategy/NullValueCheckTest.java index 957b3e7b1a..256d3f7d92 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/strategy/NullValueCheckTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/strategy/NullValueCheckTest.java @@ -5,11 +5,9 @@ */ package org.mapstruct.ap.test.nullcheck.strategy; -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; @@ -21,10 +19,9 @@ HouseMapperConfig.class, HouseMapperWithConfig.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class NullValueCheckTest { - @Test + @ProcessorTest public void testDefinedOnMapper() { HouseEntity entity = HouseMapper.INSTANCE.mapWithNvcsOnMapper( new HouseDto() ); @@ -35,7 +32,7 @@ public void testDefinedOnMapper() { } - @Test + @ProcessorTest public void testDefinedOnBean() { HouseEntity entity = HouseMapper.INSTANCE.mapWithNvcsOnBean( new HouseDto() ); @@ -46,7 +43,7 @@ public void testDefinedOnBean() { } - @Test + @ProcessorTest public void testDefinedOnMapping() { HouseEntity entity = HouseMapper.INSTANCE.mapWithNvcsOnMapping( new HouseDto() ); @@ -57,7 +54,7 @@ public void testDefinedOnMapping() { } - @Test + @ProcessorTest public void testDefinedOnConfig() { HouseEntity entity = HouseMapperWithConfig.INSTANCE.mapWithNvcsOnMapper( new HouseDto() ); @@ -68,7 +65,7 @@ public void testDefinedOnConfig() { } - @Test + @ProcessorTest public void testDefinedOnConfigAndBean() { HouseEntity entity = HouseMapperWithConfig.INSTANCE.mapWithNvcsOnBean( new HouseDto() ); @@ -79,7 +76,7 @@ public void testDefinedOnConfigAndBean() { } - @Test + @ProcessorTest public void testDefinedOnConfigAndMapping() { HouseEntity entity = HouseMapperWithConfig.INSTANCE.mapWithNvcsOnMapping( new HouseDto() ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.java new file mode 100644 index 0000000000..d239f9ace7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapper.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.nullvaluemapping; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CarListMapper { + + CarListMapper INSTANCE = Mappers.getMapper( CarListMapper.class ); + + @Mapping(target = "seatCount", ignore = true) + @Mapping(target = "model", ignore = true) + @Mapping(target = "catalogId", ignore = true) + CarDto map(Car car); + + List carsToCarDtoList(List cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.java new file mode 100644 index 0000000000..860b17654a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarListMapperSettingOnMapper.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.nullvaluemapping; + +import java.util.List; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_NULL) +public interface CarListMapperSettingOnMapper { + + CarListMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarListMapperSettingOnMapper.class ); + + @Mapping(target = "seatCount", ignore = true) + @Mapping(target = "model", ignore = true) + @Mapping(target = "catalogId", ignore = true) + CarDto map(Car car); + + List carsToCarDtoList(List cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.java new file mode 100644 index 0000000000..0227949f4d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapper.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.nullvaluemapping; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface CarMapMapper { + + CarMapMapper INSTANCE = Mappers.getMapper( CarMapMapper.class ); + + @Mapping(target = "seatCount", ignore = true) + @Mapping(target = "model", ignore = true) + @Mapping(target = "catalogId", ignore = true) + CarDto map(Car car); + + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.java new file mode 100644 index 0000000000..adb99887bd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapMapperSettingOnMapper.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.nullvaluemapping; + +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_NULL) +public interface CarMapMapperSettingOnMapper { + + CarMapMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarMapMapperSettingOnMapper.class ); + + @Mapping(target = "seatCount", ignore = true) + @Mapping(target = "model", ignore = true) + @Mapping(target = "catalogId", ignore = true) + CarDto map(Car car); + + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapper.java index f40e4fbb45..26e3f30e6b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapper.java @@ -16,7 +16,6 @@ import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; import org.mapstruct.ap.test.nullvaluemapping._target.DriverAndCarDto; import org.mapstruct.ap.test.nullvaluemapping.source.Car; @@ -29,19 +28,14 @@ public interface CarMapper { CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); @BeanMapping(nullValueMappingStrategy = RETURN_DEFAULT) - @Mappings({ - @Mapping(target = "seatCount", source = "numberOfSeats"), - @Mapping(target = "model", constant = "ModelT"), - @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") - }) + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") CarDto carToCarDto(Car car); @BeanMapping(nullValueMappingStrategy = RETURN_DEFAULT) - @Mappings({ - @Mapping(target = "seatCount", source = "car.numberOfSeats"), - @Mapping(target = "model", source = "model"), // TODO, should not be needed, must be made based on name only - @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") - }) + @Mapping(target = "seatCount", source = "car.numberOfSeats") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") CarDto carToCarDto(Car car, String model); @IterableMapping(nullValueMappingStrategy = RETURN_DEFAULT) diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnConfig.java new file mode 100644 index 0000000000..8fd9447073 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnConfig.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.nullvaluemapping; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +@Mapper(imports = UUID.class, config = CentralIterableMappingConfig.class) +public interface CarMapperIterableSettingOnConfig { + + CarMapperIterableSettingOnConfig INSTANCE = Mappers.getMapper( CarMapperIterableSettingOnConfig.class ); + + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dummy") + List carsToCarDtos(List cars); + + @MapMapping(valueDateFormat = "dummy") + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnMapper.java new file mode 100644 index 0000000000..0e1e123c52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperIterableSettingOnMapper.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.nullvaluemapping; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +@Mapper( + imports = UUID.class, + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_NULL +) +public interface CarMapperIterableSettingOnMapper { + + CarMapperIterableSettingOnMapper INSTANCE = Mappers.getMapper( CarMapperIterableSettingOnMapper.class ); + + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dummy") + List carsToCarDtos(List cars); + + @MapMapping(valueDateFormat = "dummy") + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnConfig.java new file mode 100644 index 0000000000..47f0f34f63 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnConfig.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.nullvaluemapping; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +@Mapper(imports = UUID.class, config = CentralMapMappingConfig.class) +public interface CarMapperMapSettingOnConfig { + + CarMapperMapSettingOnConfig INSTANCE = Mappers.getMapper( CarMapperMapSettingOnConfig.class ); + + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dummy") + List carsToCarDtos(List cars); + + @MapMapping(valueDateFormat = "dummy") + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnMapper.java new file mode 100644 index 0000000000..0caad062c5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperMapSettingOnMapper.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.nullvaluemapping; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.mapstruct.IterableMapping; +import org.mapstruct.MapMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.source.Car; +import org.mapstruct.factory.Mappers; + +@Mapper( + imports = UUID.class, + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_NULL +) +public interface CarMapperMapSettingOnMapper { + + CarMapperMapSettingOnMapper INSTANCE = Mappers.getMapper( CarMapperMapSettingOnMapper.class ); + + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") + CarDto carToCarDto(Car car); + + @IterableMapping(dateFormat = "dummy") + List carsToCarDtos(List cars); + + @MapMapping(valueDateFormat = "dummy") + Map carsToCarDtoMap(Map cars); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnConfig.java index f8f15a3e77..8e4ca2dac6 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnConfig.java @@ -13,7 +13,6 @@ import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; import org.mapstruct.ap.test.nullvaluemapping.source.Car; @@ -24,11 +23,9 @@ public interface CarMapperSettingOnConfig { CarMapperSettingOnConfig INSTANCE = Mappers.getMapper( CarMapperSettingOnConfig.class ); - @Mappings({ - @Mapping(target = "seatCount", source = "numberOfSeats"), - @Mapping(target = "model", constant = "ModelT"), - @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") - }) + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") CarDto carToCarDto(Car car); @IterableMapping(dateFormat = "dummy") diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnMapper.java index c3f7d5f2c7..2306b3add4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CarMapperSettingOnMapper.java @@ -13,7 +13,6 @@ import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.Mappings; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; import org.mapstruct.ap.test.nullvaluemapping.source.Car; @@ -24,11 +23,9 @@ public interface CarMapperSettingOnMapper { CarMapperSettingOnMapper INSTANCE = Mappers.getMapper( CarMapperSettingOnMapper.class ); - @Mappings({ - @Mapping(target = "seatCount", source = "numberOfSeats"), - @Mapping(target = "model", constant = "ModelT"), - @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") - }) + @Mapping(target = "seatCount", source = "numberOfSeats") + @Mapping(target = "model", constant = "ModelT") + @Mapping(target = "catalogId", expression = "java( UUID.randomUUID().toString() )") CarDto carToCarDto(Car car); @IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralIterableMappingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralIterableMappingConfig.java new file mode 100644 index 0000000000..559c3520a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralIterableMappingConfig.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.nullvaluemapping; + +import org.mapstruct.MapperConfig; +import org.mapstruct.NullValueMappingStrategy; + +@MapperConfig( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueIterableMappingStrategy = NullValueMappingStrategy.RETURN_NULL +) +public class CentralIterableMappingConfig { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralMapMappingConfig.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralMapMappingConfig.java new file mode 100644 index 0000000000..7737e07de6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/CentralMapMappingConfig.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.nullvaluemapping; + +import org.mapstruct.MapperConfig; +import org.mapstruct.NullValueMappingStrategy; + +@MapperConfig( + nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT, + nullValueMapMappingStrategy = NullValueMappingStrategy.RETURN_NULL +) +public class CentralMapMappingConfig { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.java new file mode 100644 index 0000000000..c4acecd64f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueIterableMappingStrategyTest.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.nullvaluemapping; + +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.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.compilation.annotation.ProcessorOption; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CarDto.class, + Car.class +}) +@IssueKey("2953") +public class NullValueIterableMappingStrategyTest { + + @ProcessorTest + @ProcessorOption(name = "mapstruct.nullValueIterableMappingStrategy", value = "return_default") + @WithClasses({ + CarListMapper.class + }) + void globalNullIterableMappingStrategy() { + assertThat( CarListMapper.INSTANCE.carsToCarDtoList( null ) ).isEmpty(); + } + + @ProcessorTest + @ProcessorOption(name = "mapstruct.nullValueIterableMappingStrategy", value = "return_default") + @WithClasses({ + CarListMapperSettingOnMapper.class + }) + void globalNullMapMappingStrategyWithOverrideInMapper() { + // Explicit definition in @Mapper should override global + assertThat( CarListMapperSettingOnMapper.INSTANCE.carsToCarDtoList( null ) ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.java new file mode 100644 index 0000000000..33b5942f85 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMapMappingStrategyTest.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.nullvaluemapping; + +import org.mapstruct.ap.test.nullvaluemapping._target.CarDto; +import org.mapstruct.ap.test.nullvaluemapping.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.compilation.annotation.ProcessorOption; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + CarDto.class, + Car.class +}) +@IssueKey("2953") +public class NullValueMapMappingStrategyTest { + + @ProcessorTest + @ProcessorOption(name = "mapstruct.nullValueMapMappingStrategy", value = "return_default") + @WithClasses({ + CarMapMapper.class + }) + void globalNullMapMappingStrategy() { + assertThat( CarMapMapper.INSTANCE.carsToCarDtoMap( null ) ).isEmpty(); + } + + @ProcessorTest + @ProcessorOption(name = "mapstruct.nullValueMapMappingStrategy", value = "return_default") + @WithClasses({ + CarMapMapperSettingOnMapper.class + }) + void globalNullMapMappingStrategyWithOverrideInMapper() { + // Explicit definition in @Mapper should override global + assertThat( CarMapMapperSettingOnMapper.INSTANCE.carsToCarDtoMap( null ) ).isNull(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMappingTest.java index e316f20866..a027acaba3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluemapping/NullValueMappingTest.java @@ -5,22 +5,20 @@ */ package org.mapstruct.ap.test.nullvaluemapping; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; 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.test.nullvaluemapping._target.CarDto; import org.mapstruct.ap.test.nullvaluemapping._target.DriverAndCarDto; import org.mapstruct.ap.test.nullvaluemapping.source.Car; import org.mapstruct.ap.test.nullvaluemapping.source.Driver; 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 for the strategies for mapping {@code null} values, given via {@code NullValueMapping} etc. @@ -35,18 +33,23 @@ DriverAndCarDto.class, CarMapper.class, CarMapperSettingOnMapper.class, + CarMapperIterableSettingOnMapper.class, + CarMapperMapSettingOnMapper.class, CentralConfig.class, - CarMapperSettingOnConfig.class + CarMapperSettingOnConfig.class, + CentralIterableMappingConfig.class, + CarMapperIterableSettingOnConfig.class, + CentralMapMappingConfig.class, + CarMapperMapSettingOnConfig.class, }) -@RunWith(AnnotationProcessorTestRunner.class) public class NullValueMappingTest { - @Test - public void shouldProvideMapperInstance() throws Exception { + @ProcessorTest + public void shouldProvideMapperInstance() { assertThat( CarMapper.INSTANCE ).isNotNull(); } - @Test + @ProcessorTest public void shouldMapExpressionAndConstantRegardlessNullArg() { //given Car car = new Car( "Morris", 2 ); @@ -72,7 +75,7 @@ public void shouldMapExpressionAndConstantRegardlessNullArg() { assertThat( carDto2.getCatalogId() ).isNotEmpty(); } - @Test + @ProcessorTest public void shouldMapExpressionAndConstantRegardlessNullArgSeveralSources() { //given Car car = new Car( "Morris", 2 ); @@ -83,6 +86,7 @@ public void shouldMapExpressionAndConstantRegardlessNullArgSeveralSources() { //then assertThat( carDto1 ).isNotNull(); assertThat( carDto1.getMake() ).isEqualTo( car.getMake() ); + assertThat( carDto1.getModel() ).isEqualTo( "ModelT" ); assertThat( carDto1.getSeatCount() ).isEqualTo( car.getNumberOfSeats() ); assertThat( carDto1.getCatalogId() ).isNotEmpty(); @@ -97,7 +101,7 @@ public void shouldMapExpressionAndConstantRegardlessNullArgSeveralSources() { assertThat( carDto2.getCatalogId() ).isNotEmpty(); } - @Test + @ProcessorTest public void shouldMapIterableWithNullArg() { //given @@ -118,7 +122,7 @@ public void shouldMapIterableWithNullArg() { assertThat( carDtos2.isEmpty() ).isTrue(); } - @Test + @ProcessorTest @SuppressWarnings({ "rawtypes", "unchecked" }) public void shouldMapMapWithNullArg() { @@ -142,7 +146,7 @@ public void shouldMapMapWithNullArg() { assertThat( carDtoMap2.isEmpty() ).isTrue(); } - @Test + @ProcessorTest public void shouldMapExpressionAndConstantRegardlessNullArgOnMapper() { //when @@ -156,18 +160,17 @@ public void shouldMapExpressionAndConstantRegardlessNullArgOnMapper() { assertThat( carDto.getCatalogId() ).isNotEmpty(); } - @Test + @ProcessorTest public void shouldMapIterableWithNullArgOnMapper() { //when List carDtos = CarMapperSettingOnMapper.INSTANCE.carsToCarDtos( null ); //then - assertThat( carDtos ).isNotNull(); - assertThat( carDtos.isEmpty() ).isTrue(); + assertThat( carDtos ).isEmpty(); } - @Test + @ProcessorTest public void shouldMapMapWithNullArgOnMapper() { //when @@ -177,7 +180,75 @@ public void shouldMapMapWithNullArgOnMapper() { assertThat( carDtoMap ).isNull(); } - @Test + @ProcessorTest + public void shouldMapExpressionAndConstantRegardlessOfIterableNullArgOnMapper() { + + //when + CarDto carDto = CarMapperIterableSettingOnMapper.INSTANCE.carToCarDto( null ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( 0 ); + assertThat( carDto.getModel() ).isEqualTo( "ModelT" ); + assertThat( carDto.getCatalogId() ).isNotEmpty(); + } + + @ProcessorTest + public void shouldMapIterableToNullWithIterableNullArgOnMapper() { + + //when + List carDtos = CarMapperIterableSettingOnMapper.INSTANCE.carsToCarDtos( null ); + + //then + assertThat( carDtos ).isNull(); + } + + @ProcessorTest + public void shouldMapMapRegardlessOfIterableNullArgOnMapper() { + + //when + Map carDtoMap = CarMapperIterableSettingOnMapper.INSTANCE.carsToCarDtoMap( null ); + + //then + assertThat( carDtoMap ).isEmpty(); + } + + @ProcessorTest + public void shouldMapExpressionAndConstantRegardlessMapNullArgOnMapper() { + + //when + CarDto carDto = CarMapperMapSettingOnMapper.INSTANCE.carToCarDto( null ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( 0 ); + assertThat( carDto.getModel() ).isEqualTo( "ModelT" ); + assertThat( carDto.getCatalogId() ).isNotEmpty(); + } + + @ProcessorTest + public void shouldMapIterableRegardlessOfMapNullArgOnMapper() { + + //when + List carDtos = CarMapperMapSettingOnMapper.INSTANCE.carsToCarDtos( null ); + + //then + assertThat( carDtos ).isEmpty(); + } + + @ProcessorTest + public void shouldMapMapToWithMapNullArgOnMapper() { + + //when + Map carDtoMap = CarMapperMapSettingOnMapper.INSTANCE.carsToCarDtoMap( null ); + + //then + assertThat( carDtoMap ).isNull(); + } + + @ProcessorTest public void shouldMapExpressionAndConstantRegardlessNullArgOnConfig() { //when @@ -191,18 +262,17 @@ public void shouldMapExpressionAndConstantRegardlessNullArgOnConfig() { assertThat( carDto.getCatalogId() ).isNotEmpty(); } - @Test + @ProcessorTest public void shouldMapIterableWithNullArgOnConfig() { //when List carDtos = CarMapperSettingOnConfig.INSTANCE.carsToCarDtos( null ); //then - assertThat( carDtos ).isNotNull(); - assertThat( carDtos.isEmpty() ).isTrue(); + assertThat( carDtos ).isEmpty(); } - @Test + @ProcessorTest public void shouldMapMapWithNullArgOnConfig() { //when @@ -212,7 +282,75 @@ public void shouldMapMapWithNullArgOnConfig() { assertThat( carDtoMap ).isNull(); } - @Test + @ProcessorTest + public void shouldMapExpressionAndConstantRegardlessOfIterableNullArgOnConfig() { + + //when + CarDto carDto = CarMapperIterableSettingOnConfig.INSTANCE.carToCarDto( null ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( 0 ); + assertThat( carDto.getModel() ).isEqualTo( "ModelT" ); + assertThat( carDto.getCatalogId() ).isNotEmpty(); + } + + @ProcessorTest + public void shouldMapIterableToNullWithIterableNullArgOnConfig() { + + //when + List carDtos = CarMapperIterableSettingOnConfig.INSTANCE.carsToCarDtos( null ); + + //then + assertThat( carDtos ).isNull(); + } + + @ProcessorTest + public void shouldMapMapRegardlessOfIterableNullArgOnConfig() { + + //when + Map carDtoMap = CarMapperIterableSettingOnConfig.INSTANCE.carsToCarDtoMap( null ); + + //then + assertThat( carDtoMap ).isEmpty(); + } + + @ProcessorTest + public void shouldMapExpressionAndConstantRegardlessOfMapNullArgOnConfig() { + + //when + CarDto carDto = CarMapperMapSettingOnConfig.INSTANCE.carToCarDto( null ); + + //then + assertThat( carDto ).isNotNull(); + assertThat( carDto.getMake() ).isNull(); + assertThat( carDto.getSeatCount() ).isEqualTo( 0 ); + assertThat( carDto.getModel() ).isEqualTo( "ModelT" ); + assertThat( carDto.getCatalogId() ).isNotEmpty(); + } + + @ProcessorTest + public void shouldMapIterableRegardlessOfMapNullArgOnConfig() { + + //when + List carDtos = CarMapperMapSettingOnConfig.INSTANCE.carsToCarDtos( null ); + + //then + assertThat( carDtos ).isEmpty(); + } + + @ProcessorTest + public void shouldMapMapToNullWithMapNullArgOnConfig() { + + //when + Map carDtoMap = CarMapperMapSettingOnConfig.INSTANCE.carsToCarDtoMap( null ); + + //then + assertThat( carDtoMap ).isNull(); + } + + @ProcessorTest public void shouldApplyConfiguredStrategyForMethodWithSeveralSourceParams() { //when DriverAndCarDto result = CarMapper.INSTANCE.driverAndCarToDto( null, null ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDefaultMapper.java index 362a129991..1f95649dd8 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDefaultMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerDefaultMapper.java @@ -16,10 +16,10 @@ public interface CustomerDefaultMapper { CustomerDefaultMapper INSTANCE = Mappers.getMapper( CustomerDefaultMapper.class ); - @Mapping(source = "address", target = "homeDTO.addressDTO") + @Mapping(target = "homeDTO.addressDTO", source = "address") void mapCustomer(Customer customer, @MappingTarget UserDTO userDTO); - @Mapping(source = "houseNumber", target = "houseNo", defaultValue = "0") + @Mapping(target = "houseNo", defaultValue = "0", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerMapper.java index 56b930c51a..339f70516b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerMapper.java @@ -16,10 +16,10 @@ public interface CustomerMapper { CustomerMapper INSTANCE = Mappers.getMapper( CustomerMapper.class ); - @Mapping(source = "address", target = "homeDTO.addressDTO") + @Mapping(target = "homeDTO.addressDTO", source = "address") void mapCustomer(Customer customer, @MappingTarget UserDTO userDTO); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnBeanMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnBeanMappingMethodMapper.java index 7801084639..85a685b943 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnBeanMappingMethodMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnBeanMappingMethodMapper.java @@ -21,7 +21,7 @@ public interface CustomerNvpmsOnBeanMappingMethodMapper { void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnConfigMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnConfigMapper.java index 941b7d2b43..7693c2c969 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnConfigMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnConfigMapper.java @@ -17,7 +17,7 @@ public interface CustomerNvpmsOnConfigMapper { void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnMapperMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnMapperMapper.java index 592a687c6e..1b65c6d5fc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnMapperMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsOnMapperMapper.java @@ -18,7 +18,7 @@ public interface CustomerNvpmsOnMapperMapper { void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsPropertyMappingMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsPropertyMappingMapper.java index 28eaf8f337..a6b3b021fa 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsPropertyMappingMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/CustomerNvpmsPropertyMappingMapper.java @@ -21,7 +21,7 @@ public interface CustomerNvpmsPropertyMappingMapper { @Mapping( target = "details", nullValuePropertyMappingStrategy = IGNORE) void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper1.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper1.java index 3c2dd51344..e025cfd86c 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper1.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper1.java @@ -21,7 +21,7 @@ public interface ErroneousCustomerMapper1 { @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper2.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper2.java index 4b7f2bec75..fad47afb27 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper2.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper2.java @@ -21,7 +21,7 @@ public interface ErroneousCustomerMapper2 { @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper3.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper3.java index 755b468539..1a0eae5f4f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper3.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper3.java @@ -21,7 +21,7 @@ public interface ErroneousCustomerMapper3 { @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper4.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper4.java index 88fe1a7cbf..da54f8c9b5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper4.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper4.java @@ -21,7 +21,7 @@ public interface ErroneousCustomerMapper4 { @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper5.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper5.java index 05ed1c1fe6..fc2861bc15 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper5.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/ErroneousCustomerMapper5.java @@ -21,7 +21,7 @@ public interface ErroneousCustomerMapper5 { @Mapping(target = "address", nullValuePropertyMappingStrategy = IGNORE) void map(Customer customer, @MappingTarget CustomerDTO mappingTarget); - @Mapping(source = "houseNumber", target = "houseNo") + @Mapping(target = "houseNo", source = "houseNumber") void mapCustomerHouse(Address address, @MappingTarget AddressDTO addrDTO); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NullValuePropertyMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NullValuePropertyMappingTest.java index 30920b9ae1..dfe2e14d6f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NullValuePropertyMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/NullValuePropertyMappingTest.java @@ -8,14 +8,12 @@ import java.util.Arrays; import java.util.function.BiConsumer; -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; @@ -23,7 +21,6 @@ * @author Sjaak Derksen */ @IssueKey("1306") -@RunWith(AnnotationProcessorTestRunner.class) @WithClasses({ Address.class, Customer.class, @@ -34,7 +31,7 @@ }) public class NullValuePropertyMappingTest { - @Test + @ProcessorTest @WithClasses(CustomerMapper.class) public void testStrategyAppliedOnForgedMethod() { @@ -56,31 +53,31 @@ public void testStrategyAppliedOnForgedMethod() { assertThat( userDTO.getDetails() ).containsExactly( "green hair" ); } - @Test + @ProcessorTest @WithClasses({ NvpmsConfig.class, CustomerNvpmsOnConfigMapper.class }) public void testHierarchyIgnoreOnConfig() { - testConfig( ( Customer s, CustomerDTO t ) -> CustomerNvpmsOnConfigMapper.INSTANCE.map( s, t ) ); + testConfig( CustomerNvpmsOnConfigMapper.INSTANCE::map ); } - @Test + @ProcessorTest @WithClasses(CustomerNvpmsOnMapperMapper.class) public void testHierarchyIgnoreOnMapping() { - testConfig( ( Customer s, CustomerDTO t ) -> CustomerNvpmsOnMapperMapper.INSTANCE.map( s, t ) ); + testConfig( CustomerNvpmsOnMapperMapper.INSTANCE::map ); } - @Test + @ProcessorTest @WithClasses(CustomerNvpmsOnBeanMappingMethodMapper.class) public void testHierarchyIgnoreOnBeanMappingMethod() { - testConfig( ( Customer s, CustomerDTO t ) -> CustomerNvpmsOnBeanMappingMethodMapper.INSTANCE.map( s, t ) ); + testConfig( CustomerNvpmsOnBeanMappingMethodMapper.INSTANCE::map ); } - @Test + @ProcessorTest @WithClasses(CustomerNvpmsPropertyMappingMapper.class) - public void testHierarchyIgnoreOnPropertyMappingMehtod() { - testConfig( ( Customer s, CustomerDTO t ) -> CustomerNvpmsPropertyMappingMapper.INSTANCE.map( s, t ) ); + public void testHierarchyIgnoreOnPropertyMappingMethod() { + testConfig( CustomerNvpmsPropertyMappingMapper.INSTANCE::map ); } - @Test + @ProcessorTest @WithClasses(CustomerDefaultMapper.class) public void testStrategyDefaultAppliedOnForgedMethod() { @@ -102,7 +99,7 @@ public void testStrategyDefaultAppliedOnForgedMethod() { assertThat( userDTO.getDetails() ).isEmpty(); } - @Test + @ProcessorTest @WithClasses(ErroneousCustomerMapper1.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -111,14 +108,14 @@ public void testStrategyDefaultAppliedOnForgedMethod() { kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, alternativeLine = 22, // Javac wrong error reporting on repeatable annotations JDK-8042710 - messageRegExp = "Default value and nullValuePropertyMappingStrategy are both defined in @Mapping, " + + message = "Default value and nullValuePropertyMappingStrategy are both defined in @Mapping, " + "either define a defaultValue or an nullValuePropertyMappingStrategy.") } ) public void testBothDefaultValueAndNvpmsDefined() { } - @Test + @ProcessorTest @WithClasses(ErroneousCustomerMapper2.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -127,14 +124,14 @@ public void testBothDefaultValueAndNvpmsDefined() { kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, alternativeLine = 22, // Javac wrong error reporting on repeatable annotations JDK-8042710 - messageRegExp = "Expression and nullValuePropertyMappingStrategy are both defined in @Mapping, " + + message = "Expression and nullValuePropertyMappingStrategy are both defined in @Mapping, " + "either define an expression or an nullValuePropertyMappingStrategy.") } ) public void testBothExpressionAndNvpmsDefined() { } - @Test + @ProcessorTest @WithClasses(ErroneousCustomerMapper3.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -143,14 +140,14 @@ public void testBothExpressionAndNvpmsDefined() { kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, alternativeLine = 22, // Javac wrong error reporting on repeatable annotations JDK-8042710 - messageRegExp = "DefaultExpression and nullValuePropertyMappingStrategy are both defined in " + + message = "DefaultExpression and nullValuePropertyMappingStrategy are both defined in " + "@Mapping, either define a defaultExpression or an nullValuePropertyMappingStrategy.") } ) public void testBothDefaultExpressionAndNvpmsDefined() { } - @Test + @ProcessorTest @WithClasses(ErroneousCustomerMapper4.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -159,14 +156,14 @@ public void testBothDefaultExpressionAndNvpmsDefined() { kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, alternativeLine = 22, // Javac wrong error reporting on repeatable annotations JDK-8042710 - messageRegExp = "Constant and nullValuePropertyMappingStrategy are both defined in @Mapping, " + + message = "Constant and nullValuePropertyMappingStrategy are both defined in @Mapping, " + "either define a constant or an nullValuePropertyMappingStrategy.") } ) public void testBothConstantAndNvpmsDefined() { } - @Test + @ProcessorTest @WithClasses(ErroneousCustomerMapper5.class) @ExpectedCompilationOutcome( value = CompilationResult.FAILED, @@ -175,7 +172,7 @@ public void testBothConstantAndNvpmsDefined() { kind = javax.tools.Diagnostic.Kind.ERROR, line = 20, alternativeLine = 22, // Javac wrong error reporting on repeatable annotations JDK-8042710 - messageRegExp = "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, " + + message = "Ignore and nullValuePropertyMappingStrategy are both defined in @Mapping, " + "either define ignore or an nullValuePropertyMappingStrategy.") } ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.java new file mode 100644 index 0000000000..545ddffe8b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/Bean.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.nullvaluepropertymapping.clear; + +import java.util.Collection; +import java.util.Map; + +public class Bean { + + private String id; + private Collection list; + private Map map; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Collection getList() { + return list; + } + + public void setList(Collection list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.java new file mode 100644 index 0000000000..9529da8113 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTO.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.nullvaluepropertymapping.clear; + +import java.util.Collection; +import java.util.Map; + +public class BeanDTO { + + private String id; + private Collection list; + private Map map; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Collection getList() { + return list; + } + + public void setList(Collection list) { + this.list = list; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.java new file mode 100644 index 0000000000..152a872d82 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanDTOWithId.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.nullvaluepropertymapping.clear; + +public class BeanDTOWithId extends BeanDTO { + + 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/nullvaluepropertymapping/clear/BeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.java new file mode 100644 index 0000000000..4d453579ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapper.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.nullvaluepropertymapping.clear; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface BeanMapper { + + BeanMapper INSTANCE = Mappers.getMapper( BeanMapper.class ); + + @Mapping(target = "list", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + @Mapping(target = "map", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO map(Bean source, @MappingTarget BeanDTO target); + + @Mapping(target = "id", source = "bean.id") + @Mapping(target = "list", source = "bean.list", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + @Mapping(target = "map", source = "bean.map", + nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO map(NestedBean source, @MappingTarget BeanDTO target); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) + BeanDTO mapWithBeanMapping(Bean source, @MappingTarget BeanDTO target); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.java new file mode 100644 index 0000000000..0e26b91b5b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/BeanMapperWithStrategyOnMapper.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.nullvaluepropertymapping.clear; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.CLEAR) +public interface BeanMapperWithStrategyOnMapper { + + BeanMapperWithStrategyOnMapper INSTANCE = Mappers.getMapper( BeanMapperWithStrategyOnMapper.class ); + + BeanDTO map(Bean source, @MappingTarget BeanDTO target); + + @Mapping(target = "id", source = "bean.id") + @Mapping(target = "list", source = "bean.list") + @Mapping(target = "map", source = "bean.map") + BeanDTO map(NestedBean source, @MappingTarget BeanDTO target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.java new file mode 100644 index 0000000000..448645c107 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NestedBean.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.nullvaluepropertymapping.clear; + +public class NestedBean { + private Bean bean; + + public Bean getBean() { + return bean; + } + + public void setBean(Bean bean) { + this.bean = bean; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.java new file mode 100644 index 0000000000..20cbcdc2b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullvaluepropertymapping/clear/NullValuePropertyMappingClearTest.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.nullvaluepropertymapping.clear; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Bean.class, + BeanDTO.class, + NestedBean.class, +}) +class NullValuePropertyMappingClearTest { + + @ProcessorTest + @WithClasses(BeanMapper.class) + void generatedMapperMethodsShouldCallClear() { + BeanDTO target = new BeanDTO(); + target.setId( "target" ); + List targetList = new ArrayList<>(); + targetList.add( "a" ); + targetList.add( "b" ); + target.setList( targetList ); + Map targetMap = new HashMap<>(); + targetMap.put( "a", "aValue" ); + target.setMap( targetMap ); + Bean source = new Bean(); + + BeanMapper.INSTANCE.map( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + NestedBean nestedBean = new NestedBean(); + nestedBean.setBean( source ); + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapper.INSTANCE.map( nestedBean, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapper.INSTANCE.mapWithBeanMapping( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + } + + @ProcessorTest + @WithClasses(BeanMapperWithStrategyOnMapper.class) + void generatedMapperWithMappingDefinedInConfigMethodsShouldCallClear() { + BeanDTO target = new BeanDTO(); + target.setId( "target" ); + List targetList = new ArrayList<>(); + targetList.add( "a" ); + targetList.add( "b" ); + target.setList( targetList ); + Map targetMap = new HashMap<>(); + targetMap.put( "a", "aValue" ); + target.setMap( targetMap ); + Bean source = new Bean(); + + BeanMapperWithStrategyOnMapper.INSTANCE.map( source, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + + NestedBean nestedBean = new NestedBean(); + nestedBean.setBean( source ); + targetList.add( "a" ); + targetList.add( "b" ); + targetMap.put( "a", "aValue" ); + target.setId( "target" ); + BeanMapperWithStrategyOnMapper.INSTANCE.map( nestedBean, target ); + assertThat( target.getId() ).isNull(); + assertThat( target.getList() ) + .isSameAs( targetList ) + .isEmpty(); + assertThat( target.getMap() ) + .isSameAs( targetMap ) + .isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/oneway/OnewayTest.java b/processor/src/test/java/org/mapstruct/ap/test/oneway/OnewayTest.java index 7794b406d8..f69497f3a3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/oneway/OnewayTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/oneway/OnewayTest.java @@ -5,13 +5,11 @@ */ package org.mapstruct.ap.test.oneway; -import static org.assertj.core.api.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; /** * Test for propagation of attribute without setter in source and getter in @@ -20,10 +18,9 @@ * @author Gunnar Morling */ @WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) -@RunWith(AnnotationProcessorTestRunner.class) public class OnewayTest { - @Test + @ProcessorTest @IssueKey("17") public void shouldMapAttributeWithoutSetterInSourceType() { Source source = new Source(); @@ -34,7 +31,7 @@ public void shouldMapAttributeWithoutSetterInSourceType() { assertThat( target.retrieveFoo() ).isEqualTo( Long.valueOf( 42 ) ); } - @Test + @ProcessorTest @IssueKey("41") public void shouldReverseMapAttributeWithoutSetterInTargetType() { Target target = new Target(); @@ -45,7 +42,7 @@ public void shouldReverseMapAttributeWithoutSetterInTargetType() { assertThat( source.retrieveBar() ).isEqualTo( 23 ); } - @Test + @ProcessorTest @IssueKey("104") public void shouldMapMappedAttributeWithoutSetterInSourceType() { Source source = new Source(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/oneway/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/oneway/SourceTargetMapper.java index efc62ee6ea..e8cf4c5681 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/oneway/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/oneway/SourceTargetMapper.java @@ -14,7 +14,7 @@ public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - @Mapping(source = "qax", target = "qux") + @Mapping(target = "qux", source = "qax") Target sourceToTarget(Source source); Source targetToSource(Target target); diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.java new file mode 100644 index 0000000000..30dca2cf39 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterMapper.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.optional.beforeafter; + +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalBeforeAfterMapper { + + OptionalBeforeAfterMapper INSTANCE = Mappers.getMapper( OptionalBeforeAfterMapper.class ); + + Target toTarget(Source source); + + @BeforeMapping + default void beforeDeepOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeDeepOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterDeepOptionalSourceWithNonOptionalTarget(@MappingTarget Target.SubType target, + Optional source) { + } + + @AfterMapping + default void afterDeepOptionalSourceWithOptionalTarget(@MappingTarget Optional target, + Optional source) { + } + + @AfterMapping + default void afterDeepNonOptionalSourceOptionalTarget(@MappingTarget Optional target, + Source.SubType source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNoTargetType(Optional source) { + } + + @BeforeMapping + default void beforeShallowOptionalSourceWithNonOptionalTargetType(@TargetType Class targetType, + Optional source) { + } + + @AfterMapping + default void afterShallowOptionalSourceWithNoTarget(Optional source) { + + } + + @AfterMapping + default void afterShallowOptionalSourceWithNonOptionalTarget(@MappingTarget String target, + Optional source) { + } + + @AfterMapping + default void afterShallowNonOptionalSourceOptionalTarget(@MappingTarget Optional target, String source) { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.java new file mode 100644 index 0000000000..ba1a7b09d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/OptionalBeforeAfterTest.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.optional.beforeafter; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +@WithClasses({ + OptionalBeforeAfterMapper.class, Source.class, Target.class +}) +class OptionalBeforeAfterTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalBeforeAfterMapper.class ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.java new file mode 100644 index 0000000000..1284bca7ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Source.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.optional.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional deepOptionalToOptional; + private final Optional deepOptionalToNonOptional; + private final SubType deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final Optional shallowOptionalToNonOptional; + private final String shallowNonOptionalToOptional; + + public Source(Optional deepOptionalToOptional, Optional deepOptionalToNonOptional, + SubType deepNonOptionalToOptional, Optional shallowOptionalToOptional, + Optional shallowOptionalToNonOptional, String shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public Optional getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public SubType getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public Optional getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public String getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.java new file mode 100644 index 0000000000..592e3338fe --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/beforeafter/Target.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.optional.beforeafter; + +import java.util.Objects; +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional deepOptionalToOptional; + private final SubType deepOptionalToNonOptional; + private final Optional deepNonOptionalToOptional; + + private final Optional shallowOptionalToOptional; + private final String shallowOptionalToNonOptional; + private final Optional shallowNonOptionalToOptional; + + public Target(Optional deepOptionalToOptional, SubType deepOptionalToNonOptional, + Optional deepNonOptionalToOptional, Optional shallowOptionalToOptional, + String shallowOptionalToNonOptional, Optional shallowNonOptionalToOptional) { + this.deepOptionalToOptional = deepOptionalToOptional; + this.deepOptionalToNonOptional = deepOptionalToNonOptional; + this.deepNonOptionalToOptional = deepNonOptionalToOptional; + this.shallowOptionalToOptional = shallowOptionalToOptional; + this.shallowOptionalToNonOptional = shallowOptionalToNonOptional; + this.shallowNonOptionalToOptional = shallowNonOptionalToOptional; + } + + public Optional getDeepOptionalToOptional() { + return deepOptionalToOptional; + } + + public SubType getDeepOptionalToNonOptional() { + return deepOptionalToNonOptional; + } + + public Optional getDeepNonOptionalToOptional() { + return deepNonOptionalToOptional; + } + + public Optional getShallowOptionalToOptional() { + return shallowOptionalToOptional; + } + + public String getShallowOptionalToNonOptional() { + return shallowOptionalToNonOptional; + } + + public Optional getShallowNonOptionalToOptional() { + return shallowNonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + SubType subType = (SubType) o; + return Objects.equals( value, subType.value ); + } + + @Override + public int hashCode() { + return Objects.hash( value ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.java new file mode 100644 index 0000000000..9dd4b01256 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/OptionalBuilderTest.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.optional.builder; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +class OptionalBuilderTest { + + @ProcessorTest + @WithClasses(SimpleOptionalBuilderMapper.class) + void simpleOptionalBuilder() { + Optional targetOpt = SimpleOptionalBuilderMapper.INSTANCE.map( null ); + assertThat( targetOpt ).isEmpty(); + + targetOpt = SimpleOptionalBuilderMapper.INSTANCE.map( new SimpleOptionalBuilderMapper.Source( "test" ) ); + assertThat( targetOpt ).isNotEmpty(); + SimpleOptionalBuilderMapper.Target target = targetOpt.get(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.java new file mode 100644 index 0000000000..5f24aa0cae --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/SimpleOptionalBuilderMapper.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.optional.builder; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface SimpleOptionalBuilderMapper { + + SimpleOptionalBuilderMapper INSTANCE = Mappers.getMapper( SimpleOptionalBuilderMapper.class ); + + Optional 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; + + private Target(TargetBuilder builder) { + this.value = builder.value; + } + + public String getValue() { + return value; + } + + public static TargetBuilder builder() { + return new TargetBuilder(); + } + + } + + class TargetBuilder { + + private String value; + + public TargetBuilder value(String value) { + this.value = value; + return this; + } + + public Target build() { + return new Target(this); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.java new file mode 100644 index 0000000000..a6f8c155d4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalMapper.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.optional.custom; + +import java.util.Optional; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE) +@SuppressWarnings("OptionalAssignedToNull") +public interface CustomOptionalMapper { + + CustomOptionalMapper INSTANCE = Mappers.getMapper( CustomOptionalMapper.class ); + + Target map(Source source); + + void update(@MappingTarget Target target, Source source); + + @Condition + default boolean isPresent(Optional optional) { + return optional != null; + } + + default E unwrapFromOptional(Optional optional) { + return optional == null + ? null + : optional.orElse( null ); + } + + class Target { + private String value = "initial"; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + } + + class Source { + private final Optional value; + + public Source(Optional value) { + this.value = value; + } + + public Optional getValue() { + return value; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.java new file mode 100644 index 0000000000..51991ac478 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/custom/CustomOptionalTest.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.optional.custom; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses(CustomOptionalMapper.class) +class CustomOptionalTest { + + @ProcessorTest + void shouldUseCustomMethodsWhenMapping() { + CustomOptionalMapper.Target target = CustomOptionalMapper.INSTANCE. + map( new CustomOptionalMapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "initial" ); + + target = CustomOptionalMapper.INSTANCE.map( new CustomOptionalMapper.Source( Optional.empty() ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + + target = CustomOptionalMapper.INSTANCE.map( new CustomOptionalMapper.Source( Optional.of( "test" ) ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } + + @ProcessorTest + void shouldUseCustomMethodsWhenUpdating() { + CustomOptionalMapper.Target target = new CustomOptionalMapper.Target(); + + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( null ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "initial" ); + + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( Optional.empty() ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isNull(); + + target = new CustomOptionalMapper.Target(); + CustomOptionalMapper.INSTANCE.update( target, new CustomOptionalMapper.Source( Optional.of( "test" ) ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getValue() ).isEqualTo( "test" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.java new file mode 100644 index 0000000000..5a276b4745 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesMapper.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.optional.differenttypes; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface OptionalDifferentTypesMapper { + + OptionalDifferentTypesMapper INSTANCE = Mappers.getMapper( OptionalDifferentTypesMapper.class ); + + Target toTarget(Source source); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.java new file mode 100644 index 0000000000..74ab85e174 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/OptionalDifferentTypesTest.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.test.optional.differenttypes; + +import java.util.Optional; + +import org.junit.jupiter.api.extension.RegisterExtension; +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({ + OptionalDifferentTypesMapper.class, Source.class, Target.class +}) +class OptionalDifferentTypesTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + void generatedCode() { + generatedSource.addComparisonToFixtureFor( OptionalDifferentTypesMapper.class ); + } + + @ProcessorTest + void constructorOptionalToOptionalWhenPresent() { + Source source = new Source( Optional.of( new Source.SubType( "some value" ) ), Optional.empty(), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void constructorOptionalToOptionalWhenEmpty() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void constructorOptionalToNonOptionalWhenPresent() { + Source source = new Source( Optional.empty(), Optional.of( new Source.SubType( "some value" ) ), null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.getConstructorOptionalToNonOptional(); + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void constructorOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + void constructorNonOptionalToOptionalWhenNotNull() { + Source source = new Source( Optional.empty(), Optional.empty(), new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void constructorNonOptionalToOptionalWhenNull() { + Source source = new Source(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getConstructorNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void optionalToOptionalWhenPresent() { + Source source = new Source(); + source.setOptionalToOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void optionalToOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void optionalToNonOptionalWhenPresent() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.of( new Source.SubType( "some value" ) ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.getOptionalToNonOptional(); + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void optionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.setOptionalToNonOptional( Optional.empty() ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getOptionalToNonOptional() ).isNull(); + } + + @ProcessorTest + void nonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.setNonOptionalToOptional( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void nonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.setNonOptionalToOptional( null ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.getNonOptionalToOptional() ).isEmpty(); + } + + @ProcessorTest + void publicOptionalToOptionalWhenPresent() { + Source source = new Source(); + source.publicOptionalToOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void publicOptionalToOptionalWhenEmpty() { + Source source = new Source(); + source.publicOptionalToOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToOptional ).isEmpty(); + } + + @ProcessorTest + void publicOptionalToNonOptionalWhenPresent() { + Source source = new Source(); + source.publicOptionalToNonOptional = Optional.of( new Source.SubType( "some value" ) ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + Target.SubType subType = target.publicOptionalToNonOptional; + assertThat( subType ).isNotNull(); + assertThat( subType.getValue() ).isEqualTo( "some value" ); + } + + @ProcessorTest + void publicOptionalToNonOptionalWhenEmpty() { + Source source = new Source(); + source.publicOptionalToNonOptional = Optional.empty(); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicOptionalToNonOptional ).isNull(); + } + + @ProcessorTest + void publicNonOptionalToOptionalWhenNotNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = new Source.SubType( "some value" ); + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ) + .hasValueSatisfying( subType -> + assertThat( subType.getValue() ).isEqualTo( "some value" ) ); + } + + @ProcessorTest + void publicNonOptionalToOptionalWhenNull() { + Source source = new Source(); + source.publicNonOptionalToOptional = null; + + Target target = OptionalDifferentTypesMapper.INSTANCE.toTarget( source ); + assertThat( target.publicNonOptionalToOptional ).isEmpty(); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.java new file mode 100644 index 0000000000..0c0648000a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Source.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.optional.differenttypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Source { + + private final Optional constructorOptionalToOptional; + private final Optional constructorOptionalToNonOptional; + private final SubType constructorNonOptionalToOptional; + + private Optional optionalToOptional = Optional.empty(); + private Optional optionalToNonOptional = Optional.empty(); + private SubType nonOptionalToOptional; + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional = Optional.empty(); + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToNonOptional = Optional.empty(); + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicNonOptionalToOptional; + + public Source() { + this( Optional.empty(), Optional.empty(), null ); + } + + public Source(Optional constructorOptionalToOptional, Optional constructorOptionalToNonOptional, + SubType constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public Optional getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public SubType getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public Optional getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(Optional optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public SubType getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(SubType nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.java new file mode 100644 index 0000000000..4f2b21c8a7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/differenttypes/Target.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.optional.differenttypes; + +import java.util.Optional; + +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class Target { + + private final Optional constructorOptionalToOptional; + private final SubType constructorOptionalToNonOptional; + private final Optional constructorNonOptionalToOptional; + + private Optional optionalToOptional = Optional.of( new SubType( "initial" ) ); + private SubType optionalToNonOptional; + private Optional nonOptionalToOptional = Optional.of( new SubType( "initial" ) ); + + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicOptionalToOptional = Optional.of( new SubType( "initial" ) ); + @SuppressWarnings( "VisibilityModifier" ) + public SubType publicOptionalToNonOptional; + @SuppressWarnings( "VisibilityModifier" ) + public Optional publicNonOptionalToOptional = Optional.of( new SubType( "initial" ) ); + + public Target(Optional constructorOptionalToOptional, SubType constructorOptionalToNonOptional, + Optional constructorNonOptionalToOptional) { + this.constructorOptionalToOptional = constructorOptionalToOptional; + this.constructorOptionalToNonOptional = constructorOptionalToNonOptional; + this.constructorNonOptionalToOptional = constructorNonOptionalToOptional; + } + + public Optional getConstructorOptionalToOptional() { + return constructorOptionalToOptional; + } + + public SubType getConstructorOptionalToNonOptional() { + return constructorOptionalToNonOptional; + } + + public Optional getConstructorNonOptionalToOptional() { + return constructorNonOptionalToOptional; + } + + public Optional getOptionalToOptional() { + return optionalToOptional; + } + + public void setOptionalToOptional(Optional optionalToOptional) { + this.optionalToOptional = optionalToOptional; + } + + public SubType getOptionalToNonOptional() { + return optionalToNonOptional; + } + + public void setOptionalToNonOptional(SubType optionalToNonOptional) { + this.optionalToNonOptional = optionalToNonOptional; + } + + public Optional getNonOptionalToOptional() { + return nonOptionalToOptional; + } + + public void setNonOptionalToOptional(Optional nonOptionalToOptional) { + this.nonOptionalToOptional = nonOptionalToOptional; + } + + public static class SubType { + + private final String value; + + public SubType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.java new file mode 100644 index 0000000000..3624efb00e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/MappingContext.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.optional.lifecycle; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; + +/** + * @author Filip Hrisafov + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +public class MappingContext { + + private final List invokedMethods = new ArrayList<>(); + + @BeforeMapping + public void beforeWithoutParameters() { + invokedMethods.add( "beforeWithoutParameters" ); + } + + @BeforeMapping + public void beforeWithOptionalSource(Optional source) { + invokedMethods.add( "beforeWithOptionalSource" ); + } + + @BeforeMapping + public void beforeWithSource(Source source) { + invokedMethods.add( "beforeWithSource" ); + } + + @BeforeMapping + public void beforeWithTargetType(@TargetType Class targetClass) { + invokedMethods.add( "beforeWithTargetType" ); + } + + @BeforeMapping + public void beforeWithBuilderTargetType(@TargetType Class targetBuilderClass) { + invokedMethods.add( "beforeWithBuilderTargetType" ); + } + + @BeforeMapping + public void beforeWithOptionalTarget(@MappingTarget Optional target) { + invokedMethods.add( "beforeWithOptionalTarget" ); + } + + @BeforeMapping + public void beforeWithTarget(@MappingTarget Target target) { + invokedMethods.add( "beforeWithTarget" ); + } + + @BeforeMapping + public void beforeWithTargetBuilder(@MappingTarget Target.Builder target) { + invokedMethods.add( "beforeWithTargetBuilder" ); + } + + @AfterMapping + public void afterWithoutParameters() { + invokedMethods.add( "afterWithoutParameters" ); + } + + @AfterMapping + public void afterWithOptionalSource(Optional source) { + invokedMethods.add( "afterWithOptionalSource" ); + } + + @AfterMapping + public void afterWithSource(Source source) { + invokedMethods.add( "afterWithSource" ); + } + + @AfterMapping + public void afterWithTargetType(@TargetType Class targetClass) { + invokedMethods.add( "afterWithTargetType" ); + } + + @AfterMapping + public void afterWithBuilderTargetType(@TargetType Class targetClass) { + invokedMethods.add( "afterWithBuilderTargetType" ); + } + + @AfterMapping + public void afterWithTarget(@MappingTarget Target target) { + invokedMethods.add( "afterWithTarget" ); + } + + @AfterMapping + public void afterWithTargetBuilder(@MappingTarget Target.Builder target) { + invokedMethods.add( "afterWithTargetBuilder" ); + } + + @AfterMapping + public void afterWithOptionalTarget(@MappingTarget Optional target) { + invokedMethods.add( "afterWithOptionalTarget" ); + } + + public List getInvokedMethods() { + return invokedMethods; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java new file mode 100644 index 0000000000..c80dfcb6b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalLifecycleTest.java @@ -0,0 +1,165 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + */ +@WithClasses({ + MappingContext.class, + Source.class, + Target.class, +}) +public class OptionalLifecycleTest { + + @ProcessorTest + @WithClasses(OptionalToOptionalMapper.class) + void optionalToOptional() { + MappingContext context = new MappingContext(); + + OptionalToOptionalMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToOptionalMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget", + "afterWithOptionalTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToOptionalWithBuilderMapper.class) + void optionalToOptionalWithBuilder() { + MappingContext context = new MappingContext(); + + OptionalToOptionalWithBuilderMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithBuilderTargetType" + ); + + context = new MappingContext(); + OptionalToOptionalWithBuilderMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithBuilderTargetType", + "beforeWithTargetBuilder", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithBuilderTargetType", + "afterWithTarget", + "afterWithTargetBuilder", + "afterWithOptionalTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToTypeMapper.class) + void optionalToType() { + MappingContext context = new MappingContext(); + + OptionalToTypeMapper.INSTANCE.map( Optional.empty(), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToTypeMapper.INSTANCE.map( Optional.of( new Source() ), context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + } + + @ProcessorTest + @WithClasses(OptionalToTypeMultiSourceMapper.class) + void optionalToTypeMultiSource() { + MappingContext context = new MappingContext(); + + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.empty(), null, context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType" + ); + + context = new MappingContext(); + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.of( new Source() ), null, context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + + context = new MappingContext(); + OptionalToTypeMultiSourceMapper.INSTANCE.map( Optional.empty(), "Test", context ); + + assertThat( context.getInvokedMethods() ) + .containsExactlyInAnyOrder( + "beforeWithoutParameters", + "beforeWithOptionalSource", + "beforeWithTargetType", + "beforeWithTarget", + "afterWithoutParameters", + "afterWithOptionalSource", + "afterWithTargetType", + "afterWithTarget" + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.java new file mode 100644 index 0000000000..d7c5d08173 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalMapper.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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToOptionalMapper { + + OptionalToOptionalMapper INSTANCE = Mappers.getMapper( OptionalToOptionalMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Optional map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.java new file mode 100644 index 0000000000..8cfab7fd00 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToOptionalWithBuilderMapper.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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToOptionalWithBuilderMapper { + + OptionalToOptionalWithBuilderMapper INSTANCE = Mappers.getMapper( OptionalToOptionalWithBuilderMapper.class ); + + Optional map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.java new file mode 100644 index 0000000000..6a6f3e096e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMapper.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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToTypeMapper { + + OptionalToTypeMapper INSTANCE = Mappers.getMapper( OptionalToTypeMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Target map(Optional source, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.java new file mode 100644 index 0000000000..d672f2ec05 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/OptionalToTypeMultiSourceMapper.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.optional.lifecycle; + +import java.util.Optional; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Builder; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface OptionalToTypeMultiSourceMapper { + + OptionalToTypeMultiSourceMapper INSTANCE = Mappers.getMapper( OptionalToTypeMultiSourceMapper.class ); + + @BeanMapping(builder = @Builder(disableBuilder = true)) + Target map(Optional source, String otherValue, @Context MappingContext context); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Source.java new file mode 100644 index 0000000000..c7005c6ca6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/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.optional.lifecycle; + +/** + * @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/optional/lifecycle/Target.java b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.java new file mode 100644 index 0000000000..d5f68a1844 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/lifecycle/Target.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.optional.lifecycle; + +/** + * @author Filip Hrisafov + */ +public class Target { + + private String value; + + 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 Target build() { + Target target = new Target(); + target.setValue( value ); + return target; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java b/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.java new file mode 100644 index 0000000000..e6ce6faa65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/nested/Artist.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.optional.nested; + +import java.util.Optional; + +/** + * @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 boolean hasName() { + return name != null; + } + + public String getName() { + return name; + } + + public boolean hasLabel() { + return label != null; + } + + public Optional

        + * Test classes are safe to be executed in parallel, but test methods are not safe to be executed in parallel. + *

        + * By default this template would generate tests for the JDK and Eclipse Compiler. + * If only a single compiler is needed then specify the compiler in the value. + *

        + * The classes to be compiled for a given test method must be specified via {@link WithClasses}. In addition the + * following things can be configured optionally : + *

          + *
        • Processor options to be considered during compilation via + * {@link org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption ProcessorOption}.
        • + *
        • The expected compilation outcome and expected diagnostics can be specified via + * {@link org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome ExpectedCompilationOutcome}. + * If no outcome is specified, a successful compilation is assumed.
        • + *
        + * + * @author Filip Hrisafov + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@TestTemplate +@ExtendWith(ProcessorTestExtension.class) +public @interface ProcessorTest { + + Compiler[] value() default { + Compiler.JDK, + Compiler.ECLIPSE + }; +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithCdi.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithCdi.java new file mode 100644 index 0000000000..6da78fbfe0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithCdi.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.testutil; + +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; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "javax.inject", + "cdi-api", +}) +public @interface WithCdi { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaCdi.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaCdi.java new file mode 100644 index 0000000000..f22a1d26e1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaCdi.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.testutil; + +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; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "jakarta.inject-api", + "jakarta.enterprise.cdi-api", +}) +public @interface WithJakartaCdi { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaInject.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaInject.java new file mode 100644 index 0000000000..ccb0b030a1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaInject.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.testutil; + +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; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "jakarta.inject-api", +}) +public @interface WithJakartaInject { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaJaxb.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaJaxb.java new file mode 100644 index 0000000000..86c9d83a54 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJakartaJaxb.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.testutil; + +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; + +/** + * Meta annotation that adds the needed Jakarta XML Binding dependencies. + * + * @author Iaroslav Bogdanchikov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "jakarta.xml.bind", +}) +public @interface WithJakartaJaxb { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxInject.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxInject.java new file mode 100644 index 0000000000..de2ae231e7 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxInject.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.testutil; + +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; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "javax.inject", +}) +public @interface WithJavaxInject { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxJaxb.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxJaxb.java new file mode 100644 index 0000000000..a404187b52 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJavaxJaxb.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.testutil; + +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; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "jaxb-api", +}) +public @interface WithJavaxJaxb { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithJoda.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithJoda.java new file mode 100644 index 0000000000..5b8bede20b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithJoda.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.testutil; + +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; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "joda-time" +}) +public @interface WithJoda { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.java new file mode 100644 index 0000000000..f4bd2c5970 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlin.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.testutil; + +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; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithProcessorDependency({ + "kotlin-stdlib", + "kotlin-metadata-jvm", +}) +@WithTestDependency("kotlin-stdlib") +public @interface WithKotlin { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.java new file mode 100644 index 0000000000..656dc9bc5f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithKotlinSources.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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.intellij.lang.annotations.Language; + +/** + * Specifies the kotlin sources to compile during an annotation processor test. + * If given both on the class-level and the method-level for a given test, all the given sources will be compiled. + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface WithKotlinSources { + + /** + * The classes to be compiled for the annotated test class or method. + * + * @return the classes to be compiled for the annotated test class or method + */ + @Language(value = "file-reference", prefix = "", suffix = ".kt") + String[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.java new file mode 100644 index 0000000000..cccb30b6b4 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependencies.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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Filip Hrisafov + * @see WithProcessorDependency + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +public @interface WithProcessorDependencies { + + WithProcessorDependency[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.java new file mode 100644 index 0000000000..8dbf8c8acd --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithProcessorDependency.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.testutil; + +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; + +/** + * Specifies the group id of the additional dependencies that should be added to the processor path + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Repeatable(WithProcessorDependencies.class) +public @interface WithProcessorDependency { + /** + * @return The group ids of the additional dependencies for the processor path + */ + String[] value(); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.java new file mode 100644 index 0000000000..04e387d429 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithSpring.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.testutil; + +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 org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; + +/** + * Meta annotation that adds the needed Spring Dependencies + * + * @author Filip Hrisafov + */ +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@WithTestDependency({ + "spring-beans", + "spring-context" +}) +@DisabledOnJre(JRE.OTHER) +public @interface WithSpring { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependencies.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependencies.java new file mode 100644 index 0000000000..c9e8d8c523 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependencies.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.testutil; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Filip Hrisafov + * @see WithTestDependency + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +public @interface WithTestDependencies { + + WithTestDependency[] value(); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependency.java b/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependency.java new file mode 100644 index 0000000000..26d478b666 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/WithTestDependency.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.testutil; + +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; + +/** + * Specifies the group id of the additional test dependencies that should be added to the test compile path + * + * @author Filip Hrisafov + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE, ElementType.METHOD }) +@Repeatable( WithTestDependencies.class ) +public @interface WithTestDependency { + /** + * @return The group ids of the additional test dependencies for the test compile path + */ + String[] value(); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java index 62325ac8f7..8daefd2a4e 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/assertions/JavaFileAssert.java @@ -5,26 +5,21 @@ */ package org.mapstruct.ap.testutil.assertions; -import static java.lang.String.format; +import org.assertj.core.api.FileAssert; +import org.assertj.core.error.ShouldHaveSameContent; +import org.assertj.core.internal.Diff; +import org.assertj.core.internal.Failures; +import org.assertj.core.util.diff.Delta; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import org.assertj.core.api.AbstractCharSequenceAssert; -import org.assertj.core.api.Assertions; -import org.assertj.core.api.FileAssert; -import org.assertj.core.error.ShouldHaveSameContent; -import org.assertj.core.internal.Diff; -import org.assertj.core.internal.Failures; -import org.assertj.core.util.diff.Delta; - -import com.google.common.base.Charsets; -import com.google.common.io.Files; +import static java.lang.String.format; /** * Allows to perform assertions on .java source files. @@ -35,13 +30,13 @@ public class JavaFileAssert extends FileAssert { private static final String FIRST_LINE_LICENSE_REGEX = ".*Copyright MapStruct Authors.*"; private static final String GENERATED_DATE_REGEX = "\\s+date = " + - "\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+\\d{4}\","; + "\"\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[+-]\\d{4}\","; private static final String GENERATED_COMMENTS_REGEX = "\\s+comments = \"version: , compiler: .*, environment: " + ".*\""; private static final String IMPORT_GENERATED_ANNOTATION_REGEX = "import javax\\.annotation\\.(processing\\.)?" + "Generated;"; - private Diff diff = new Diff(); + private final Diff diff = new Diff(); /** * @param actual the actual file @@ -50,22 +45,6 @@ public JavaFileAssert(File actual) { super( actual ); } - /** - * @return assertion on the file content - */ - public AbstractCharSequenceAssert content() { - exists(); - isFile(); - - try { - return Assertions.assertThat( Files.toString( actual, Charsets.UTF_8 ) ); - } - catch ( IOException e ) { - failWithMessage( "Unable to read" + actual.toString() + ". Exception: " + e.getMessage() ); - } - return null; - } - /** * Verifies that the specified class is imported in this Java file * @@ -92,21 +71,15 @@ public void containsNoImportFor(Class importedClass) { * @param expected the file that should be matched */ public void hasSameMapperContent(File expected) { - Charset charset = Charset.forName( "UTF-8" ); + Charset charset = StandardCharsets.UTF_8; try { - List> diffs = new ArrayList>( this.diff.diff( + List> diffs = new ArrayList<>( this.diff.diff( actual, charset, expected, charset ) ); - Iterator> iterator = diffs.iterator(); - while ( iterator.hasNext() ) { - Delta delta = iterator.next(); - if ( ignoreDelta( delta ) ) { - iterator.remove(); - } - } + diffs.removeIf( this::ignoreDelta ); if ( !diffs.isEmpty() ) { throw Failures.instance() .failure( info, ShouldHaveSameContent.shouldHaveSameContent( actual, expected, diffs ) ); @@ -126,7 +99,6 @@ public void hasSameMapperContent(File expected) { * or if it is a change delta for the date/comments part of a {@code @Generated} annotation. * * @param delta that needs to be checked - * * @return {@code true} if this delta should be ignored, {@code false} otherwise */ private boolean ignoreDelta(Delta delta) { diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/Diagnostic.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/Diagnostic.java index b2016f1c2d..982c3e42e7 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/Diagnostic.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/Diagnostic.java @@ -45,6 +45,14 @@ */ long alternativeLine() default -1; + /** + * A message matching the exact expected message of the diagnostic. + * + * @return A message matching the exact expected message of the + * diagnostic. + */ + String message() default ""; + /** * A regular expression matching the expected message of the diagnostic. * Wild-cards matching any character (".*") will be added to the beginning diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java index 1cb95f52c2..2070c028c7 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/annotation/ExpectedNote.java @@ -29,7 +29,7 @@ */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) - public @interface ExpectedNotes { + @interface ExpectedNotes { /** * Regexp for the note to match. diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java index 22d9bcd68e..b5a4dc535d 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/CompilationOutcomeDescriptor.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -17,6 +18,8 @@ import org.codehaus.plexus.compiler.CompilerMessage; import org.codehaus.plexus.compiler.CompilerResult; +import org.jetbrains.kotlin.cli.common.ExitCode; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; @@ -28,8 +31,6 @@ */ public class CompilationOutcomeDescriptor { - private static final String LINE_SEPARATOR = System.lineSeparator( ); - private CompilationResult compilationResult; private List diagnostics; private List notes; @@ -57,12 +58,12 @@ public static CompilationOutcomeDescriptor forExpectedCompilationResult( if ( expectedCompilationResult == null ) { return new CompilationOutcomeDescriptor( CompilationResult.SUCCEEDED, - Collections.emptyList(), + Collections.emptyList(), notes ); } else { - List diagnosticDescriptors = new ArrayList(); + List diagnosticDescriptors = new ArrayList<>(); for ( org.mapstruct.ap.testutil.compilation.annotation.Diagnostic diagnostic : expectedCompilationResult.diagnostics() ) { diagnosticDescriptors.add( DiagnosticDescriptor.forDiagnostic( diagnostic ) ); @@ -76,7 +77,7 @@ public static CompilationOutcomeDescriptor forResult(String sourceDir, boolean c CompilationResult compilationResult = compilationSuccessful ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; List notes = new ArrayList<>(); - List diagnosticDescriptors = new ArrayList(); + List diagnosticDescriptors = new ArrayList<>(); for ( Diagnostic diagnostic : diagnostics ) { //ignore notes created by the compiler if ( diagnostic.getKind() != Kind.NOTE ) { @@ -93,7 +94,7 @@ public static CompilationOutcomeDescriptor forResult(String sourceDir, boolean c public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerResult compilerResult) { CompilationResult compilationResult = compilerResult.isSuccess() ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; - List diagnosticDescriptors = new ArrayList(); + List diagnosticDescriptors = new ArrayList<>(); for ( CompilerMessage message : compilerResult.getCompilerMessages() ) { if ( message.getKind() != CompilerMessage.Kind.NOTE ) { @@ -105,6 +106,25 @@ public static CompilationOutcomeDescriptor forResult(String sourceDir, CompilerR return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, Collections.emptyList() ); } + public static CompilationOutcomeDescriptor forResult(String sourceDir, ExitCode exitCode, + List messages) { + List notes = new ArrayList<>(); + List diagnosticDescriptors = new ArrayList<>( messages.size() ); + for ( MessageCollectorImpl.Message message : messages ) { + //ignore notes created by the compiler + DiagnosticDescriptor descriptor = DiagnosticDescriptor.forCompilerMessage( sourceDir, message ); + if ( descriptor.getKind() != Kind.NOTE ) { + diagnosticDescriptors.add( descriptor ); + } + else { + notes.add( descriptor.getMessage() ); + } + } + CompilationResult compilationResult = + exitCode == ExitCode.OK ? CompilationResult.SUCCEEDED : CompilationResult.FAILED; + return new CompilationOutcomeDescriptor( compilationResult, diagnosticDescriptors, notes ); + } + public CompilationResult getCompilationResult() { return compilationResult; } @@ -117,6 +137,16 @@ public List getNotes() { return notes; } + public CompilationOutcomeDescriptor merge(CompilationOutcomeDescriptor other) { + CompilationResult compilationResult = + this.compilationResult == CompilationResult.SUCCEEDED ? other.compilationResult : this.compilationResult; + List diagnostics = new ArrayList<>( this.diagnostics ); + diagnostics.addAll( other.diagnostics ); + List notes = new ArrayList<>( this.notes ); + notes.addAll( other.notes ); + return new CompilationOutcomeDescriptor( compilationResult, diagnostics, notes ); + } + @Override public int hashCode() { final int prime = 31; @@ -145,12 +175,7 @@ public boolean equals(Object obj) { if ( compilationResult != other.compilationResult ) { return false; } - if ( diagnostics == null ) { - if ( other.diagnostics != null ) { - return false; - } - } - else if ( !diagnostics.equals( other.diagnostics ) ) { + if ( !Objects.equals( diagnostics, other.diagnostics ) ) { return false; } return true; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java index f023610642..68e2cb00fe 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java @@ -9,11 +9,15 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.util.Objects; import javax.tools.Diagnostic.Kind; import javax.tools.JavaFileObject; import org.codehaus.plexus.compiler.CompilerMessage; +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity; +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; /** @@ -28,28 +32,32 @@ public class DiagnosticDescriptor { private final Long line; private final Long alternativeLine; private final String message; + private final String messageRegex; - private DiagnosticDescriptor(String sourceFileName, Kind kind, Long line, String message) { - this( sourceFileName, kind, line, null, message ); + private DiagnosticDescriptor(String sourceFileName, Kind kind, Long line, String message, String messageRegex) { + this( sourceFileName, kind, line, null, message, messageRegex ); } - private DiagnosticDescriptor(String sourceFileName, Kind kind, Long line, Long alternativeLine, String message) { + private DiagnosticDescriptor(String sourceFileName, Kind kind, Long line, Long alternativeLine, String message, + String messageRegex) { this.sourceFileName = sourceFileName; this.kind = kind; this.line = line; this.alternativeLine = alternativeLine; this.message = message; + this.messageRegex = messageRegex; } public static DiagnosticDescriptor forDiagnostic(Diagnostic diagnostic) { - String soureFileName = diagnostic.type() != void.class + String sourceFileName = diagnostic.type() != void.class ? diagnostic.type().getName().replace( ".", File.separator ) + ".java" : null; return new DiagnosticDescriptor( - soureFileName, + sourceFileName, diagnostic.kind(), diagnostic.line() != -1 ? diagnostic.line() : null, diagnostic.alternativeLine() != -1 ? diagnostic.alternativeLine() : null, + diagnostic.message(), diagnostic.messageRegExp() ); } @@ -60,7 +68,8 @@ public static DiagnosticDescriptor forDiagnostic(String sourceDir, getSourceName( sourceDir, diagnostic ), diagnostic.getKind(), diagnostic.getLineNumber(), - diagnostic.getMessage( null ) + diagnostic.getMessage( null ), + null ); } @@ -72,7 +81,9 @@ public static DiagnosticDescriptor forCompilerMessage(String sourceDir, Compiler removeSourceDirPrefix( sourceDir, compilerMessage.getFile() ), toJavaxKind( compilerMessage.getKind() ), Long.valueOf( compilerMessage.getStartLine() ), - message ); + message, + null + ); } private static Kind toJavaxKind(CompilerMessage.Kind kind) { @@ -92,6 +103,37 @@ private static Kind toJavaxKind(CompilerMessage.Kind kind) { } } + public static DiagnosticDescriptor forCompilerMessage(String sourceDir, MessageCollectorImpl.Message message) { + CompilerMessageSourceLocation location = message.getLocation(); + String sourceFileName; + Long line; + if ( location != null ) { + sourceFileName = location.getPath(); + line = (long) location.getLine(); + } + else { + sourceFileName = null; + line = null; + } + return new DiagnosticDescriptor( + sourceFileName, + toJavaxKind( message.getSeverity() ), + line, + message.getMessage(), + null + ); + } + + private static Kind toJavaxKind(CompilerMessageSeverity severity) { + return switch ( severity ) { + case ERROR, EXCEPTION -> Kind.ERROR; + case WARNING, FIXED_WARNING -> Kind.WARNING; + case STRONG_WARNING -> Kind.MANDATORY_WARNING; + case INFO, LOGGING -> Kind.NOTE; + case OUTPUT -> Kind.OTHER; + }; + } + private static String getSourceName(String sourceDir, javax.tools.Diagnostic diagnostic) { if ( diagnostic.getSource() == null ) { return null; @@ -123,7 +165,7 @@ private static URI getUri(javax.tools.Diagnostic diagn //Make the URI absolute in case it isn't (the case with JDK 6) try { - return new URI( "file:" + uri.toString() ); + return new URI( "file:" + uri ); } catch ( URISyntaxException e ) { throw new RuntimeException( e ); @@ -150,6 +192,10 @@ public String getMessage() { return message; } + public String getMessageRegex() { + return messageRegex; + } + @Override public int hashCode() { final int prime = 31; @@ -177,23 +223,13 @@ public boolean equals(Object obj) { if ( kind != other.kind ) { return false; } - if ( line != other.line ) { + if ( !Objects.equals( line, other.line ) ) { return false; } - if ( message == null ) { - if ( other.message != null ) { - return false; - } - } - else if ( !message.equals( other.message ) ) { + if ( !Objects.equals( message, other.message ) ) { return false; } - if ( sourceFileName == null ) { - if ( other.sourceFileName != null ) { - return false; - } - } - else if ( !sourceFileName.equals( other.sourceFileName ) ) { + if ( !Objects.equals( sourceFileName, other.sourceFileName ) ) { return false; } return true; diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java deleted file mode 100644 index 2688f9f7df..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.List; - -import org.junit.runner.Description; -import org.junit.runner.Runner; -import org.junit.runner.manipulation.Filter; -import org.junit.runner.manipulation.NoTestsRemainException; -import org.junit.runner.notification.RunNotifier; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.ParentRunner; -import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; - -/** - * A JUnit4 runner for Annotation Processor tests. - *

        - * Test classes are safe to be executed in parallel, but test methods are not safe to be executed in parallel. - *

        - * The classes to be compiled for a given test method must be specified via {@link WithClasses}. In addition the - * following things can be configured optionally : - *

          - *
        • Processor options to be considered during compilation via {@link ProcessorOption}.
        • - *
        • The expected compilation outcome and expected diagnostics can be specified via {@link ExpectedCompilationOutcome} - * . If no outcome is specified, a successful compilation is assumed.
        • - *
        - * - * @author Gunnar Morling - * @author Andreas Gudian - */ -public class AnnotationProcessorTestRunner extends ParentRunner { - - private static final boolean IS_AT_LEAST_JAVA_9 = isIsAtLeastJava9(); - - private static boolean isIsAtLeastJava9() { - try { - Runtime.class.getMethod( "version" ); - return true; - } - catch ( NoSuchMethodException e ) { - return false; - } - } - - private final List runners; - - /** - * @param klass the test class - * - * @throws Exception see {@link BlockJUnit4ClassRunner#BlockJUnit4ClassRunner(Class)} - */ - public AnnotationProcessorTestRunner(Class klass) throws Exception { - super( klass ); - - runners = createRunners( klass ); - } - - @SuppressWarnings("deprecation") - private List createRunners(Class klass) throws Exception { - WithSingleCompiler singleCompiler = klass.getAnnotation( WithSingleCompiler.class ); - - if (singleCompiler != null) { - return Arrays. asList( new InnerAnnotationProcessorRunner( klass, singleCompiler.value() ) ); - } - else if ( IS_AT_LEAST_JAVA_9 ) { - // Current tycho-compiler-jdt (0.26.0) is not compatible with Java 11 - // Updating to latest version 1.3.0 fails some tests - // Once https://github.com/mapstruct/mapstruct/pull/1587 is resolved we can remove this line - return Arrays.asList( new InnerAnnotationProcessorRunner( klass, Compiler.JDK11 ) ); - } - - return Arrays. asList( - new InnerAnnotationProcessorRunner( klass, Compiler.JDK ), - new InnerAnnotationProcessorRunner( klass, Compiler.ECLIPSE ) - ); - } - - @Override - protected List getChildren() { - return runners; - } - - @Override - protected Description describeChild(Runner child) { - return child.getDescription(); - } - - @Override - protected void runChild(Runner child, RunNotifier notifier) { - child.run( notifier ); - } - - @Override - public void filter(Filter filter) throws NoTestsRemainException { - super.filter( new FilterDecorator( filter ) ); - } - - /** - * Allows to only execute selected methods, even if the executing framework is not aware of parameterized tests - * (e.g. some versions of IntelliJ, Netbeans, Eclipse). - */ - private static final class FilterDecorator extends Filter { - private final Filter delegate; - - private FilterDecorator(Filter delegate) { - this.delegate = delegate; - } - - @Override - public boolean shouldRun(Description description) { - boolean shouldRun = delegate.shouldRun( description ); - if ( !shouldRun ) { - return delegate.shouldRun( withoutParameterizedName( description ) ); - } - - return shouldRun; - } - - @Override - public String describe() { - return delegate.describe(); - } - - private Description withoutParameterizedName(Description description) { - String cleanDispayName = removeParameter( description.getDisplayName() ); - Description cleanDescription = - Description.createSuiteDescription( - cleanDispayName, - description.getAnnotations().toArray( new Annotation[description.getAnnotations().size()] ) ); - - for ( Description child : description.getChildren() ) { - cleanDescription.addChild( withoutParameterizedName( child ) ); - } - - return cleanDescription; - } - - private String removeParameter(String name) { - if ( name.startsWith( "[" ) ) { - return name; - } - - // remove "[compiler]" from "method[compiler](class)" - int open = name.indexOf( '[' ); - int close = name.indexOf( ']' ) + 1; - return name.substring( 0, open ) + name.substring( close ); - } - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java index 46a112da2e..c9718c6c21 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilationRequest.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.testutil.runner; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; @@ -13,23 +14,36 @@ * Represents a compilation task for a number of sources with given processor options. */ public class CompilationRequest { + private final Compiler compiler; private final Set> sourceClasses; private final Map, Class> services; private final List processorOptions; + private final Collection testDependencies; + private final Collection processorDependencies; + private final Collection kotlinSources; - CompilationRequest(Set> sourceClasses, Map, Class> services, List processorOptions) { + CompilationRequest(Compiler compiler, Set> sourceClasses, Map, Class> services, + List processorOptions, Collection testDependencies, + Collection processorDependencies, + Collection kotlinSources) { + this.compiler = compiler; this.sourceClasses = sourceClasses; this.services = services; this.processorOptions = processorOptions; + this.testDependencies = testDependencies; + this.processorDependencies = processorDependencies; + this.kotlinSources = kotlinSources; } @Override public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + ( ( compiler == null ) ? 0 : compiler.hashCode() ); result = prime * result + ( ( processorOptions == null ) ? 0 : processorOptions.hashCode() ); result = prime * result + ( ( services == null ) ? 0 : services.hashCode() ); result = prime * result + ( ( sourceClasses == null ) ? 0 : sourceClasses.hashCode() ); + result = prime * result + ( ( testDependencies == null ) ? 0 : testDependencies.hashCode() ); return result; } @@ -46,9 +60,13 @@ public boolean equals(Object obj) { } CompilationRequest other = (CompilationRequest) obj; - return processorOptions.equals( other.processorOptions ) + return compiler.equals( other.compiler ) + && processorOptions.equals( other.processorOptions ) && services.equals( other.services ) - && sourceClasses.equals( other.sourceClasses ); + && testDependencies.equals( other.testDependencies ) + && processorDependencies.equals( other.processorDependencies ) + && sourceClasses.equals( other.sourceClasses ) + && kotlinSources.equals( other.kotlinSources ); } public Set> getSourceClasses() { @@ -62,4 +80,20 @@ public List getProcessorOptions() { public Map, Class> getServices() { return services; } + + public Collection getTestDependencies() { + return testDependencies; + } + + public Collection getProcessorDependencies() { + return processorDependencies; + } + + public Collection getKotlinSources() { + return kotlinSources; + } + + public Compiler getCompiler() { + return compiler; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/Compiler.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/Compiler.java index d59a7ed0dc..e38ae28775 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/Compiler.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/Compiler.java @@ -5,10 +5,27 @@ */ package org.mapstruct.ap.testutil.runner; +import org.junit.jupiter.api.condition.JRE; + /** * @author Andreas Gudian - * + * @author Filip Hrisafov */ public enum Compiler { - JDK, JDK11, ECLIPSE; + JDK, + ECLIPSE; + + private final JRE latestSupportedJre; + + Compiler() { + this( JRE.OTHER ); + } + + Compiler(JRE latestSupportedJre) { + this.latestSupportedJre = latestSupportedJre; + } + + public JRE latestSupportedJre() { + return latestSupportedJre; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilerLauncherDiscoveryListener.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilerLauncherDiscoveryListener.java new file mode 100644 index 0000000000..fde6d0d599 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilerLauncherDiscoveryListener.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.testutil.runner; + +import org.junit.platform.engine.UniqueId; +import org.junit.platform.launcher.LauncherDiscoveryListener; + +/** + * @author Filip Hrisafov + */ +public class CompilerLauncherDiscoveryListener implements LauncherDiscoveryListener { + @Override + public void engineDiscoveryStarted(UniqueId engineId) { + // Currently JUnit Jupiter does not have an SPI for providing a ClassLoader for loading the class + // However, we can change the current context class loaded when the engine discovery starts. + // This would make sure that JUnit Jupiter uses our ClassLoader to correctly load the mappers + ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); + FilteringParentClassLoader filteringParentClassLoader = new FilteringParentClassLoader( + currentClassLoader, + "org.mapstruct.ap.test." + ); + ModifiableURLClassLoader newClassLoader = new ModifiableURLClassLoader( filteringParentClassLoader ); + newClassLoader.withOriginOf( getClass() ); + Thread.currentThread().setContextClassLoader( newClassLoader ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilerTestEnabledOnJreCondition.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilerTestEnabledOnJreCondition.java new file mode 100644 index 0000000000..29e3280e8b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilerTestEnabledOnJreCondition.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.testutil.runner; + +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Every compiler is registered with it's max supported JRE that it can run on. + * This condition is used to check if the test for a particular compiler can be run with the current JRE. + * + * @author Filip Hrisafov + */ +public class CompilerTestEnabledOnJreCondition 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" ) ); + + protected final Compiler compiler; + + public CompilerTestEnabledOnJreCondition(Compiler compiler) { + this.compiler = compiler; + } + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { + // If the max JRE is greater or equal to the current version the test is enabled + return compiler.latestSupportedJre().compareTo( JRE.currentVersion() ) >= 0 ? ENABLED_ON_CURRENT_JRE : + DISABLED_ON_CURRENT_JRE; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java new file mode 100644 index 0000000000..cb12be725c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -0,0 +1,731 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.testutil.runner; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.SourceVersion; + +import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean; +import com.puppycrawl.tools.checkstyle.Checker; +import com.puppycrawl.tools.checkstyle.ConfigurationLoader; +import com.puppycrawl.tools.checkstyle.DefaultLogger; +import com.puppycrawl.tools.checkstyle.PropertiesExpander; +import org.apache.commons.io.output.NullOutputStream; +import org.jetbrains.kotlin.cli.common.ExitCode; +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments; +import org.jetbrains.kotlin.cli.common.messages.MessageCollectorImpl; +import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler; +import org.jetbrains.kotlin.config.Services; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithKotlinSources; +import org.mapstruct.ap.testutil.WithProcessorDependency; +import org.mapstruct.ap.testutil.WithServiceImplementation; +import org.mapstruct.ap.testutil.WithTestDependency; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.DisableCheckstyle; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; +import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; +import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; +import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor; +import org.xml.sax.InputSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation; +import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations; + +/** + * A JUnit Jupiter Extension that performs source generation using the annotation processor and compiles those sources. + * + * @author Andreas Gudian + * @author Filip Hrisafov + */ +abstract class CompilingExtension implements BeforeEachCallback { + + static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.create( new Object() ); + + private static final String TARGET_COMPILATION_TESTS = "/target/compilation-tests/"; + + private static final String LINE_SEPARATOR = System.lineSeparator( ); + + private static final DiagnosticDescriptorComparator COMPARATOR = new DiagnosticDescriptorComparator(); + + protected static final String SOURCE_DIR = getBasePath() + "/src/test/java"; + + protected static final List TEST_COMPILATION_CLASSPATH = buildTestCompilationClasspath(); + + protected static final List PROCESSOR_CLASSPATH = buildProcessorClasspath(); + + private String classOutputDir; + private String sourceOutputDir; + private String additionalCompilerClasspath; + private final Compiler compiler; + + protected CompilingExtension(Compiler compiler) { + this.compiler = compiler; + } + + protected void setupDirectories(Method testMethod, Class testClass) { + String compilationRoot = getBasePath() + + TARGET_COMPILATION_TESTS + + testClass.getName() + + "/" + testMethod.getName() + + getPathSuffix(); + + classOutputDir = compilationRoot + "/classes"; + sourceOutputDir = compilationRoot + "/generated-sources"; + additionalCompilerClasspath = compilationRoot + "/compiler"; + + createOutputDirs(); + + ( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).withPath( classOutputDir ); + } + + protected String getPathSuffix() { + return "_" + compiler.name().toLowerCase(); + } + + /** + * Build the default test compilation classpath + * needed for compiling the generated sources once the processor has run. + */ + private static List buildTestCompilationClasspath() { + Collection whitelist = Arrays.asList( + // MapStruct annotations in multi-module reactor build or IDE + "core" + File.separator + "target", + // MapStruct annotations in single module build + "org" + File.separator + "mapstruct" + File.separator + "mapstruct" + File.separator, + "guava" + ); + + return filterBootClassPath( whitelist ); + } + + /** + * Build the annotation processor classpath. + * i.e. the classpath that is needed to run the annotation processor with its own dependencies only. + * The optional dependencies are not needed in this classpath. + */ + private static List buildProcessorClasspath() { + Collection whitelist = Arrays.asList( + "processor" + File.separator + "target", // the processor itself, + "freemarker", + "gem-api" + ); + + return filterBootClassPath( whitelist ); + } + + protected static List filterBootClassPath(Collection whitelist) { + String[] bootClasspath = + System.getProperty( "java.class.path" ).split( File.pathSeparator ); + String testClasses = "target" + File.separator + "test-classes"; + + List classpath = new ArrayList<>(); + for ( String path : bootClasspath ) { + if ( !path.contains( testClasses ) && isWhitelisted( path, whitelist ) ) { + classpath.add( path ); + } + } + + return classpath; + } + + protected static Collection getProcessorClasspathDependencies(CompilationRequest request, + String additionalCompilerClasspath) { + List processClassPaths = filterBootClassPath( request.getProcessorDependencies() ); + if ( additionalCompilerClasspath != null ) { + processClassPaths.add( additionalCompilerClasspath ); + } + return processClassPaths; + } + + private static boolean isWhitelisted(String path, Collection whitelist) { + return whitelist.stream().anyMatch( path::contains ); + } + + @Override + public void beforeEach(ExtensionContext context) throws Exception { + CompilationOutcomeDescriptor actualResult = compile( context ); + assertResult( actualResult, context ); + } + + private void assertResult(CompilationOutcomeDescriptor actualResult, ExtensionContext context) throws Exception { + Method testMethod = context.getRequiredTestMethod(); + Class testClass = context.getRequiredTestClass(); + + CompilationOutcomeDescriptor expectedResult = + CompilationOutcomeDescriptor.forExpectedCompilationResult( + findAnnotation( testMethod, ExpectedCompilationOutcome.class ).orElse( null ), + findAnnotation( testMethod, ExpectedNote.ExpectedNotes.class ).orElse( null ), + findAnnotation( testMethod, ExpectedNote.class ).orElse( null ) + ); + + if ( expectedResult.getCompilationResult() == CompilationResult.SUCCEEDED ) { + assertThat( actualResult.getCompilationResult() ).describedAs( + "Compilation failed. Diagnostics: " + actualResult.getDiagnostics() + ).isEqualTo( + CompilationResult.SUCCEEDED + ); + } + else { + assertThat( actualResult.getCompilationResult() ).describedAs( + "Compilation succeeded but should have failed." + ).isEqualTo( CompilationResult.FAILED ); + } + + assertDiagnostics( actualResult.getDiagnostics(), expectedResult.getDiagnostics() ); + assertNotes( actualResult.getNotes(), expectedResult.getNotes() ); + + if ( !findAnnotation( testClass, DisableCheckstyle.class ).isPresent() && !skipCheckstyleBySystemProperty() ) { + assertCheckstyleRules(); + } + } + + private static boolean skipCheckstyleBySystemProperty() { + return Boolean.parseBoolean( System.getProperty( "checkstyle.skip" ) ); + } + + private void assertCheckstyleRules() throws Exception { + if ( sourceOutputDir != null ) { + Properties properties = new Properties(); + properties.put( "checkstyle.cache.file", classOutputDir + "/checkstyle.cache" ); + + final Checker checker = new Checker(); + checker.setModuleClassLoader( Checker.class.getClassLoader() ); + checker.configure( ConfigurationLoader.loadConfiguration( + new InputSource( getClass().getClassLoader().getResourceAsStream( + "checkstyle-for-generated-sources.xml" ) ), + new PropertiesExpander( properties ), + ConfigurationLoader.IgnoredModulesOptions.OMIT + ) ); + + ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); + checker.addListener( + new DefaultLogger( + NullOutputStream.INSTANCE, + AbstractAutomaticBean.OutputStreamOptions.CLOSE, + errorStream, + AbstractAutomaticBean.OutputStreamOptions.CLOSE + ) + ); + + int errors = checker.process( findGeneratedFiles( new File( sourceOutputDir ) ) ); + if ( errors > 0 ) { + String errorLog = errorStream.toString( StandardCharsets.UTF_8 ); + fail( "Expected checkstyle compliant output, but got errors:\n" + errorLog ); + } + } + } + + private static List findGeneratedFiles(File file) { + final List files = new LinkedList<>(); + + if ( file.canRead() ) { + if ( file.isDirectory() ) { + for ( File element : file.listFiles() ) { + files.addAll( findGeneratedFiles( element ) ); + } + } + else if ( file.isFile() ) { + files.add( file ); + } + } + return files; + } + + private void assertNotes(List actualNotes, List expectedNotes) { + List expectedNotesRemaining = new ArrayList<>( expectedNotes ); + Iterator expectedNotesIterator = expectedNotesRemaining.iterator(); + if ( expectedNotesIterator.hasNext() ) { + String expectedNoteRegexp = expectedNotesIterator.next(); + for ( String actualNote : actualNotes ) { + if ( actualNote.matches( expectedNoteRegexp ) ) { + expectedNotesIterator.remove(); + if ( expectedNotesIterator.hasNext() ) { + expectedNoteRegexp = expectedNotesIterator.next(); + } + else { + break; + } + } + } + } + + assertThat( expectedNotesRemaining ) + .describedAs( "There are unmatched notes: " + + String.join( LINE_SEPARATOR, expectedNotesRemaining ) ) + .isEmpty(); + } + + private void assertDiagnostics(List actualDiagnostics, + List expectedDiagnostics) { + + actualDiagnostics.sort( COMPARATOR ); + expectedDiagnostics.sort( COMPARATOR ); + expectedDiagnostics = filterExpectedDiagnostics( expectedDiagnostics ); + + Iterator actualIterator = actualDiagnostics.iterator(); + Iterator expectedIterator = expectedDiagnostics.iterator(); + + assertThat( actualDiagnostics ).describedAs( + String.format( + "Numbers of expected and actual diagnostics are diffent. Actual:%s%s%sExpected:%s%s.", + LINE_SEPARATOR, + actualDiagnostics.toString().replace( ", ", LINE_SEPARATOR ), + LINE_SEPARATOR, + LINE_SEPARATOR, + expectedDiagnostics.toString().replace( ", ", LINE_SEPARATOR ) + ) + ).hasSize( + expectedDiagnostics.size() + ); + + while ( actualIterator.hasNext() ) { + + DiagnosticDescriptor actual = actualIterator.next(); + DiagnosticDescriptor expected = expectedIterator.next(); + + if ( expected.getSourceFileName() != null ) { + assertThat( actual.getSourceFileName() ).isEqualTo( expected.getSourceFileName() ); + } + if ( expected.getLine() != null && expected.getAlternativeLine() != null ) { + assertThat( actual.getLine() ).isIn( expected.getLine(), expected.getAlternativeLine() ); + } + else if ( expected.getLine() != null ) { + assertThat( actual.getLine() ).as( actual.getMessage() ).isEqualTo( expected.getLine() ); + } + assertThat( actual.getKind() ) + .as( actual.getMessage() ) + .isEqualTo( expected.getKind() ); + if ( expected.getMessage() != null && !expected.getMessage().isEmpty() ) { + assertThat( actual.getMessage() ).describedAs( + String.format( + "Unexpected message for diagnostic %s:%s %s", + actual.getSourceFileName(), + actual.getLine(), + actual.getKind() + ) + ).isEqualTo( expected.getMessage() ); + } + else { + assertThat( actual.getMessage() ).describedAs( + String.format( + "Unexpected message for diagnostic %s:%s %s", + actual.getSourceFileName(), + actual.getLine(), + actual.getKind() + ) + ).matches( "(?ms).*" + expected.getMessageRegex() + ".*" ); + } + } + } + + /** + * @param expectedDiagnostics expected diagnostics + * @return a possibly filtered list of expected diagnostics + */ + protected List filterExpectedDiagnostics(List expectedDiagnostics) { + return expectedDiagnostics; + } + + /** + * Returns the classes to be compiled for this test. + * + * @return A set containing the classes to be compiled for this test + */ + private Set> getTestClasses(Method testMethod, Class testClass) { + Set> testClasses = new HashSet<>(); + + findAnnotation( testMethod, WithClasses.class ) + .ifPresent( withClasses -> testClasses.addAll( Arrays.asList( withClasses.value() ) ) ); + + findAnnotation( testClass, WithClasses.class ) + .ifPresent( withClasses -> testClasses.addAll( Arrays.asList( withClasses.value() ) ) ); + + if ( testClasses.isEmpty() ) { + throw new IllegalStateException( + "The classes to be compiled during the test must be specified via @WithClasses." + ); + } + + return testClasses; + } + + /** + * Returns the resources to be compiled for this test. + * + * @return A map containing the package were to look for a resource (key) and the resource (value) to be compiled + * for this test + */ + private Map, Class> getServices(Method testMethod, Class testClass) { + Map, Class> services = new HashMap<>(); + + addServices( services, findRepeatableAnnotations( testMethod, WithServiceImplementation.class ) ); + + addServices( services, findRepeatableAnnotations( testClass, WithServiceImplementation.class ) ); + + return services; + } + + private Collection getAdditionalTestDependencies(Method testMethod, Class testClass) { + Collection testDependencies = new HashSet<>(); + findRepeatableAnnotations( testMethod, WithTestDependency.class ) + .forEach( annotation -> Collections.addAll( testDependencies, annotation.value() ) ); + + findRepeatableAnnotations( testClass, WithTestDependency.class ) + .forEach( annotation -> Collections.addAll( testDependencies, annotation.value() ) ); + + return testDependencies; + } + + private Collection getAdditionalProcessorDependencies(Method testMethod, Class testClass) { + Collection processorDependencies = new HashSet<>(); + findRepeatableAnnotations( testMethod, WithProcessorDependency.class ) + .forEach( annotation -> Collections.addAll( processorDependencies, annotation.value() ) ); + + findRepeatableAnnotations( testClass, WithProcessorDependency.class ) + .forEach( annotation -> Collections.addAll( processorDependencies, annotation.value() ) ); + + return processorDependencies; + } + + private void addServices(Map, Class> services, List withImplementations) { + for ( WithServiceImplementation withImplementation : withImplementations ) { + addService( services, withImplementation ); + } + } + + private void addService(Map, Class> services, WithServiceImplementation annoation) { + if ( annoation == null ) { + return; + } + + Class provides = annoation.provides(); + Class implementor = annoation.value(); + if ( provides == Object.class ) { + Class[] implemented = implementor.getInterfaces(); + if ( implemented.length != 1 ) { + throw new IllegalArgumentException( + "The class " + implementor.getName() + + " either needs to implement exactly one interface, or \"provides\" needs to be specified" + + " as well in the annotation " + WithServiceImplementation.class.getSimpleName() + "." ); + } + + provides = implemented[0]; + } + + services.put( provides, implementor ); + } + + /** + * Returns the processor options to be used this test. + * + * @return A list containing the processor options to be used for this test + */ + private List getProcessorOptions(Method testMethod, Class testClass) { + List processorOptions = findRepeatableAnnotations( testMethod, ProcessorOption.class ); + + if ( processorOptions.isEmpty() ) { + processorOptions = findRepeatableAnnotations( testClass, ProcessorOption.class ); + } + + List result = new ArrayList<>( processorOptions.size() ); + for ( ProcessorOption option : processorOptions ) { + result.add( asOptionString( option ) ); + } + + // Add all debugging info to class files + result.add( "-g:source,lines,vars" ); + + return result; + } + + private String asOptionString(ProcessorOption processorOption) { + return String.format( "-A%s=%s", processorOption.name(), processorOption.value() ); + } + + protected static Set getSourceFiles(Collection> classes) { + Set sourceFiles = new HashSet<>( classes.size() ); + + for ( Class clazz : classes ) { + sourceFiles.add( + new File( + SOURCE_DIR + File.separator + clazz.getName().replace( ".", File.separator ) + + ".java" + ) + ); + } + + return sourceFiles; + } + + private Collection getKotlinSources(ExtensionContext context) { + Collection kotlinSources = new HashSet<>(); + Method testMethod = context.getRequiredTestMethod(); + Class testClass = context.getRequiredTestClass(); + + findAnnotation( testMethod, WithKotlinSources.class ) + .ifPresent( withKotlinSources -> Collections.addAll( kotlinSources, withKotlinSources.value() ) ); + + findAnnotation( testClass, WithKotlinSources.class ) + .ifPresent( withKotlinSources -> Collections.addAll( kotlinSources, withKotlinSources.value() ) ); + + return kotlinSources; + } + + private CompilationOutcomeDescriptor compile(ExtensionContext context) { + Method testMethod = context.getRequiredTestMethod(); + Class testClass = context.getRequiredTestClass(); + + CompilationRequest compilationRequest = new CompilationRequest( + compiler, + getTestClasses( testMethod, testClass ), + getServices( testMethod, testClass ), + getProcessorOptions( testMethod, testClass ), + getAdditionalTestDependencies( testMethod, testClass ), + getAdditionalProcessorDependencies( testMethod, testClass ), + getKotlinSources( context ) + ); + + ExtensionContext.Store rootStore = context.getRoot().getStore( NAMESPACE ); + + // We need to put the compilation request in the store, so the GeneratedSource can use it + context.getStore( NAMESPACE ).put( context.getUniqueId() + "-compilationRequest", compilationRequest ); + CompilationCache compilationCache = rootStore + .getOrComputeIfAbsent( compilationRequest, request -> new CompilationCache(), CompilationCache.class ); + + if ( !needsRecompilation( compilationRequest, compilationCache ) ) { + return compilationCache.getLastResult(); + } + + setupDirectories( testMethod, testClass ); + compilationCache.setLastSourceOutputDir( sourceOutputDir ); + + boolean needsAdditionalCompilerClasspath = prepareServices( compilationRequest ); + CompilationOutcomeDescriptor resultHolder; + + resultHolder = compile( + context, + compilationRequest, + sourceOutputDir, + classOutputDir, + needsAdditionalCompilerClasspath ? additionalCompilerClasspath : null ); + + compilationCache.update( compilationRequest, resultHolder ); + return resultHolder; + } + + protected Object loadAndInstantiate(ClassLoader processorClassloader, Class clazz) { + try { + return processorClassloader.loadClass( clazz.getName() ).newInstance(); + } + catch ( Exception e ) { + throw new RuntimeException( e ); + } + } + + protected CompilationOutcomeDescriptor compile( + ExtensionContext context, + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir, + String additionalCompilerClasspath) { + CompilationOutcomeDescriptor kotlinCompilationOutcome = compileWithKotlin( + context, + compilationRequest, + sourceOutputDir, + classOutputDir + ); + + if ( kotlinCompilationOutcome != null && + kotlinCompilationOutcome.getCompilationResult() == CompilationResult.FAILED ) { + return kotlinCompilationOutcome; + } + + CompilationOutcomeDescriptor javaCompilationOutcome = compileWithSpecificCompiler( + compilationRequest, + sourceOutputDir, + classOutputDir, + additionalCompilerClasspath + ); + return kotlinCompilationOutcome == null ? javaCompilationOutcome : + kotlinCompilationOutcome.merge( javaCompilationOutcome ); + } + + private CompilationOutcomeDescriptor compileWithKotlin( + ExtensionContext context, + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir + ) { + CompilationOutcomeDescriptor kotlinCompilationOutcome = null; + if ( !compilationRequest.getKotlinSources().isEmpty() ) { + K2JVMCompiler k2JvmCompiler = new K2JVMCompiler(); + MessageCollectorImpl messageCollector = new MessageCollectorImpl(); + K2JVMCompilerArguments k2JvmArguments = new K2JVMCompilerArguments(); + k2JvmArguments.setJdkRelease( getKotlinJvmTarget() ); + k2JvmArguments.setClasspath( + String.join( + File.pathSeparator, filterBootClassPath( List.of( + "kotlin-stdlib", + "kotlin-reflect" + ) ) + ) + ); + k2JvmArguments.setNoStdlib( true ); + k2JvmArguments.setNoReflect( true ); + String packageName = context.getRequiredTestClass() + .getPackageName(); + String sourcePrefix = + SOURCE_DIR + File.separator + packageName.replace( ".", File.separator ) + File.separator; + k2JvmArguments.setFreeArgs( compilationRequest.getKotlinSources() + .stream() + .map( kotlinSource -> sourcePrefix + kotlinSource ) + .collect( Collectors.toList() ) + ); + k2JvmArguments.setVerbose( true ); + k2JvmArguments.setSuppressWarnings( false ); + k2JvmArguments.setDestination( classOutputDir ); + + ExitCode kotlinExitCode = k2JvmCompiler.exec( messageCollector, Services.EMPTY, k2JvmArguments ); + + kotlinCompilationOutcome = CompilationOutcomeDescriptor.forResult( + sourceOutputDir, + kotlinExitCode, + messageCollector.getMessages() + ); + + } + return kotlinCompilationOutcome; + } + + private static String getKotlinJvmTarget() { + SourceVersion latest = SourceVersion.latest(); + if ( latest.compareTo( SourceVersion.RELEASE_21 ) >= 0 ) { + return "21"; + } + return latest.name().replace( "RELEASE_", "" ); + } + + protected abstract CompilationOutcomeDescriptor compileWithSpecificCompiler( + CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir, + String additionalCompilerClasspath); + + boolean needsRecompilation(CompilationRequest compilationRequest, CompilationCache compilationCache) { + return !compilationRequest.equals( compilationCache.getLastRequest() ); + } + + private static String getBasePath() { + try { + return new File( "." ).getCanonicalPath(); + } + catch ( IOException e ) { + throw new RuntimeException( e ); + } + } + + private void createOutputDirs() { + File directory = new File( classOutputDir ); + deleteDirectory( directory ); + directory.mkdirs(); + + directory = new File( sourceOutputDir ); + deleteDirectory( directory ); + directory.mkdirs(); + + directory = new File( additionalCompilerClasspath ); + deleteDirectory( directory ); + directory.mkdirs(); + } + + private void deleteDirectory(File path) { + if ( path.exists() ) { + File[] files = path.listFiles(); + for ( File file : files ) { + if ( file.isDirectory() ) { + deleteDirectory( file ); + } + else { + file.delete(); + } + } + } + path.delete(); + } + + private boolean prepareServices(CompilationRequest compilationRequest) { + if ( !compilationRequest.getServices().isEmpty() ) { + String servicesDir = + additionalCompilerClasspath + File.separator + "META-INF" + File.separator + "services"; + File directory = new File( servicesDir ); + deleteDirectory( directory ); + directory.mkdirs(); + for ( Map.Entry, Class> serviceEntry : compilationRequest.getServices().entrySet() ) { + try { + File file = new File( servicesDir + File.separator + serviceEntry.getKey().getName() ); + FileWriter fileWriter = new FileWriter( file ); + fileWriter.append( serviceEntry.getValue().getName() ).append( "\n" ); + fileWriter.flush(); + fileWriter.close(); + } + catch ( IOException e ) { + throw new RuntimeException( e ); + } + } + + return true; + } + + return false; + } + + private static class DiagnosticDescriptorComparator implements Comparator { + + @Override + public int compare(DiagnosticDescriptor o1, DiagnosticDescriptor o2) { + String sourceFileName1 = o1.getSourceFileName() != null ? o1.getSourceFileName() : ""; + String sourceFileName2 = o2.getSourceFileName() != null ? o2.getSourceFileName() : ""; + + int result = sourceFileName1.compareTo( sourceFileName2 ); + + if ( result != 0 ) { + return result; + } + result = o1.getLine().compareTo( o2.getLine() ); + if ( result != 0 ) { + return result; + } + + return o1.getKind().compareTo( o2.getKind() ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java deleted file mode 100644 index 0b2d297384..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingStatement.java +++ /dev/null @@ -1,598 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.stream.Collectors; - -import com.puppycrawl.tools.checkstyle.api.AutomaticBean; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.Statement; -import org.mapstruct.ap.testutil.WithClasses; -import org.mapstruct.ap.testutil.WithServiceImplementation; -import org.mapstruct.ap.testutil.WithServiceImplementations; -import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; -import org.mapstruct.ap.testutil.compilation.annotation.DisableCheckstyle; -import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; -import org.mapstruct.ap.testutil.compilation.annotation.ExpectedNote; -import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption; -import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOptions; -import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; -import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor; -import org.xml.sax.InputSource; - -import com.google.common.collect.Lists; -import com.google.common.io.ByteStreams; -import com.puppycrawl.tools.checkstyle.Checker; -import com.puppycrawl.tools.checkstyle.ConfigurationLoader; -import com.puppycrawl.tools.checkstyle.DefaultLogger; -import com.puppycrawl.tools.checkstyle.PropertiesExpander; - -/** - * A JUnit4 statement that performs source generation using the annotation processor and compiles those sources. - * - * @author Andreas Gudian - */ -abstract class CompilingStatement extends Statement { - - private static final String TARGET_COMPILATION_TESTS = "/target/compilation-tests/"; - - private static final String LINE_SEPARATOR = System.lineSeparator( ); - - private static final DiagnosticDescriptorComparator COMPARATOR = new DiagnosticDescriptorComparator(); - - protected static final String SOURCE_DIR = getBasePath() + "/src/test/java"; - - protected static final List TEST_COMPILATION_CLASSPATH = buildTestCompilationClasspath(); - - protected static final List PROCESSOR_CLASSPATH = buildProcessorClasspath(); - - private final FrameworkMethod method; - private final CompilationCache compilationCache; - private final boolean runCheckstyle; - private Statement next; - - private String classOutputDir; - private String sourceOutputDir; - private String additionalCompilerClasspath; - private CompilationRequest compilationRequest; - - CompilingStatement(FrameworkMethod method, CompilationCache compilationCache) { - this.method = method; - this.compilationCache = compilationCache; - this.runCheckstyle = !method.getMethod().getDeclaringClass().isAnnotationPresent( DisableCheckstyle.class ); - - this.compilationRequest = new CompilationRequest( getTestClasses(), getServices(), getProcessorOptions() ); - } - - void setNextStatement(Statement next) { - this.next = next; - } - - @Override - public void evaluate() throws Throwable { - generateMapperImplementation(); - - GeneratedSource.setCompilingStatement( this ); - next.evaluate(); - GeneratedSource.clearCompilingStatement(); - } - - String getSourceOutputDir() { - return compilationCache.getLastSourceOutputDir(); - } - - protected void setupDirectories() throws Exception { - String compilationRoot = getBasePath() - + TARGET_COMPILATION_TESTS - + method.getDeclaringClass().getName() - + "/" + method.getName() - + getPathSuffix(); - - classOutputDir = compilationRoot + "/classes"; - sourceOutputDir = compilationRoot + "/generated-sources"; - additionalCompilerClasspath = compilationRoot + "/compiler"; - - createOutputDirs(); - - ( (ModifiableURLClassLoader) Thread.currentThread().getContextClassLoader() ).withPath( classOutputDir ); - } - - protected abstract String getPathSuffix(); - - private static List buildTestCompilationClasspath() { - String[] whitelist = - new String[] { - // MapStruct annotations in multi-module reactor build or IDE - "core" + File.separator + "target", - // MapStruct annotations in single module build - "org" + File.separator + "mapstruct" + File.separator + "mapstruct" + File.separator, - "guava", - "javax.inject", - "spring-beans", - "spring-context", - "jaxb-api", - "joda-time" }; - - return filterBootClassPath( whitelist ); - } - - private static List buildProcessorClasspath() { - String[] whitelist = - new String[] { - "processor" + File.separator + "target", // the processor itself, - "freemarker", - "javax.inject", - "spring-context", - "joda-time" }; - - return filterBootClassPath( whitelist ); - } - - protected static List filterBootClassPath(String[] whitelist) { - String[] bootClasspath = - System.getProperty( "java.class.path" ).split( File.pathSeparator ); - String testClasses = "target" + File.separator + "test-classes"; - - List classpath = new ArrayList(); - for ( String path : bootClasspath ) { - if ( !path.contains( testClasses ) && isWhitelisted( path, whitelist ) ) { - classpath.add( path ); - } - } - - return classpath; - } - - private static boolean isWhitelisted(String path, String[] whitelist) { - for ( String whitelisted : whitelist ) { - if ( path.contains( whitelisted ) ) { - return true; - } - } - return false; - } - - protected void generateMapperImplementation() throws Exception { - CompilationOutcomeDescriptor actualResult = compile(); - - CompilationOutcomeDescriptor expectedResult = - CompilationOutcomeDescriptor.forExpectedCompilationResult( - method.getAnnotation( ExpectedCompilationOutcome.class ), - method.getAnnotation( ExpectedNote.ExpectedNotes.class ), - method.getAnnotation( ExpectedNote.class ) - ); - - if ( expectedResult.getCompilationResult() == CompilationResult.SUCCEEDED ) { - assertThat( actualResult.getCompilationResult() ).describedAs( - "Compilation failed. Diagnostics: " + actualResult.getDiagnostics() - ).isEqualTo( - CompilationResult.SUCCEEDED - ); - } - else { - assertThat( actualResult.getCompilationResult() ).describedAs( - "Compilation succeeded but should have failed." - ).isEqualTo( CompilationResult.FAILED ); - } - - assertDiagnostics( actualResult.getDiagnostics(), expectedResult.getDiagnostics() ); - assertNotes( actualResult.getNotes(), expectedResult.getNotes() ); - - if ( runCheckstyle ) { - assertCheckstyleRules(); - } - } - - private void assertCheckstyleRules() throws Exception { - if ( sourceOutputDir != null ) { - Properties properties = new Properties(); - properties.put( "checkstyle.cache.file", classOutputDir + "/checkstyle.cache" ); - - final Checker checker = new Checker(); - checker.setModuleClassLoader( Checker.class.getClassLoader() ); - checker.configure( ConfigurationLoader.loadConfiguration( - new InputSource( getClass().getClassLoader().getResourceAsStream( - "checkstyle-for-generated-sources.xml" ) ), - new PropertiesExpander( properties ), - ConfigurationLoader.IgnoredModulesOptions.OMIT - ) ); - - ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); - checker.addListener( - new DefaultLogger( - ByteStreams.nullOutputStream(), - AutomaticBean.OutputStreamOptions.CLOSE, - errorStream, - AutomaticBean.OutputStreamOptions.CLOSE - ) - ); - - int errors = checker.process( findGeneratedFiles( new File( sourceOutputDir ) ) ); - if ( errors > 0 ) { - String errorLog = errorStream.toString( "UTF-8" ); - assertThat( true ).describedAs( "Expected checkstyle compliant output, but got errors:\n" + errorLog ) - .isEqualTo( false ); - } - } - } - - private static List findGeneratedFiles(File file) { - final List files = Lists.newLinkedList(); - - if ( file.canRead() ) { - if ( file.isDirectory() ) { - for ( File element : file.listFiles() ) { - files.addAll( findGeneratedFiles( element ) ); - } - } - else if ( file.isFile() ) { - files.add( file ); - } - } - return files; - } - - private void assertNotes(List actualNotes, List expectedNotes) { - List expectedNotesRemaining = new ArrayList<>( expectedNotes ); - Iterator expectedNotesIterator = expectedNotesRemaining.iterator(); - if ( expectedNotesIterator.hasNext() ) { - String expectedNoteRegexp = expectedNotesIterator.next(); - for ( String actualNote : actualNotes ) { - if ( actualNote.matches( expectedNoteRegexp ) ) { - expectedNotesIterator.remove(); - if ( expectedNotesIterator.hasNext() ) { - expectedNoteRegexp = expectedNotesIterator.next(); - } - else { - break; - } - } - } - } - - assertThat( expectedNotesRemaining ) - .describedAs( "There are unmatched notes: " + - expectedNotesRemaining.stream().collect( Collectors.joining( LINE_SEPARATOR ) ).toString() ) - .isEmpty(); - } - - private void assertDiagnostics(List actualDiagnostics, - List expectedDiagnostics) { - - Collections.sort( actualDiagnostics, COMPARATOR ); - Collections.sort( expectedDiagnostics, COMPARATOR ); - expectedDiagnostics = filterExpectedDiagnostics( expectedDiagnostics ); - - Iterator actualIterator = actualDiagnostics.iterator(); - Iterator expectedIterator = expectedDiagnostics.iterator(); - - assertThat( actualDiagnostics ).describedAs( - String.format( - "Numbers of expected and actual diagnostics are diffent. Actual:%s%s%sExpected:%s%s.", - LINE_SEPARATOR, - actualDiagnostics.toString().replace( ", ", LINE_SEPARATOR ), - LINE_SEPARATOR, - LINE_SEPARATOR, - expectedDiagnostics.toString().replace( ", ", LINE_SEPARATOR ) - ) - ).hasSize( - expectedDiagnostics.size() - ); - - while ( actualIterator.hasNext() ) { - - DiagnosticDescriptor actual = actualIterator.next(); - DiagnosticDescriptor expected = expectedIterator.next(); - - if ( expected.getSourceFileName() != null ) { - assertThat( actual.getSourceFileName() ).isEqualTo( expected.getSourceFileName() ); - } - if ( expected.getLine() != null && expected.getAlternativeLine() != null ) { - assertThat( actual.getLine() ).isIn( expected.getLine(), expected.getAlternativeLine() ); - } - else if ( expected.getLine() != null ) { - assertThat( actual.getLine() ).isEqualTo( expected.getLine() ); - } - assertThat( actual.getKind() ).isEqualTo( expected.getKind() ); - assertThat( actual.getMessage() ).describedAs( - String.format( - "Unexpected message for diagnostic %s:%s %s", - actual.getSourceFileName(), - actual.getLine(), - actual.getKind() - ) - ).matches( "(?ms).*" + expected.getMessage() + ".*" ); - } - } - - /** - * @param expectedDiagnostics expected diagnostics - * @return a possibly filtered list of expected diagnostics - */ - protected List filterExpectedDiagnostics(List expectedDiagnostics) { - return expectedDiagnostics; - } - - /** - * Returns the classes to be compiled for this test. - * - * @return A set containing the classes to be compiled for this test - */ - private Set> getTestClasses() { - Set> testClasses = new HashSet>(); - - WithClasses withClasses = method.getAnnotation( WithClasses.class ); - if ( withClasses != null ) { - testClasses.addAll( Arrays.asList( withClasses.value() ) ); - } - - withClasses = method.getMethod().getDeclaringClass().getAnnotation( WithClasses.class ); - if ( withClasses != null ) { - testClasses.addAll( Arrays.asList( withClasses.value() ) ); - } - - if ( testClasses.isEmpty() ) { - throw new IllegalStateException( - "The classes to be compiled during the test must be specified via @WithClasses." - ); - } - - return testClasses; - } - - /** - * Returns the resources to be compiled for this test. - * - * @return A map containing the package were to look for a resource (key) and the resource (value) to be compiled - * for this test - */ - private Map, Class> getServices() { - Map, Class> services = new HashMap, Class>(); - - addServices( services, method.getAnnotation( WithServiceImplementations.class ) ); - addService( services, method.getAnnotation( WithServiceImplementation.class ) ); - - Class declaringClass = method.getMethod().getDeclaringClass(); - addServices( services, declaringClass.getAnnotation( WithServiceImplementations.class ) ); - addService( services, declaringClass.getAnnotation( WithServiceImplementation.class ) ); - - return services; - } - - private void addServices(Map, Class> services, WithServiceImplementations withImplementations) { - if ( withImplementations != null ) { - for ( WithServiceImplementation resource : withImplementations.value() ) { - addService( services, resource ); - } - } - } - - private void addService(Map, Class> services, WithServiceImplementation annoation) { - if ( annoation == null ) { - return; - } - - Class provides = annoation.provides(); - Class implementor = annoation.value(); - if ( provides == Object.class ) { - Class[] implemented = implementor.getInterfaces(); - if ( implemented.length != 1 ) { - throw new IllegalArgumentException( - "The class " + implementor.getName() - + " either needs to implement exactly one interface, or \"provides\" needs to be specified" - + " as well in the annotation " + WithServiceImplementation.class.getSimpleName() + "." ); - } - - provides = implemented[0]; - } - - services.put( provides, implementor ); - } - - /** - * Returns the processor options to be used this test. - * - * @return A list containing the processor options to be used for this test - */ - private List getProcessorOptions() { - List processorOptions = - getProcessorOptions( - method.getAnnotation( ProcessorOptions.class ), - method.getAnnotation( ProcessorOption.class ) ); - - if ( processorOptions.isEmpty() ) { - processorOptions = - getProcessorOptions( - method.getMethod().getDeclaringClass().getAnnotation( ProcessorOptions.class ), - method.getMethod().getDeclaringClass().getAnnotation( ProcessorOption.class ) ); - } - - List result = new ArrayList( processorOptions.size() ); - for ( ProcessorOption option : processorOptions ) { - result.add( asOptionString( option ) ); - } - - // Add all debugging info to class files - result.add( "-g:source,lines,vars" ); - - return result; - } - - private List getProcessorOptions(ProcessorOptions options, ProcessorOption option) { - if ( options != null ) { - return Arrays.asList( options.value() ); - } - else if ( option != null ) { - return Arrays.asList( option ); - } - - return Collections.emptyList(); - } - - private String asOptionString(ProcessorOption processorOption) { - return String.format( "-A%s=%s", processorOption.name(), processorOption.value() ); - } - - protected static Set getSourceFiles(Collection> classes) { - Set sourceFiles = new HashSet( classes.size() ); - - for ( Class clazz : classes ) { - sourceFiles.add( - new File( - SOURCE_DIR + File.separator + clazz.getName().replace( ".", File.separator ) - + ".java" - ) - ); - } - - return sourceFiles; - } - - private CompilationOutcomeDescriptor compile() - throws Exception { - - if ( !needsRecompilation() ) { - return compilationCache.getLastResult(); - } - - setupDirectories(); - compilationCache.setLastSourceOutputDir( sourceOutputDir ); - - boolean needsAdditionalCompilerClasspath = prepareServices(); - CompilationOutcomeDescriptor resultHolder; - - resultHolder = compileWithSpecificCompiler( - compilationRequest, - sourceOutputDir, - classOutputDir, - needsAdditionalCompilerClasspath ? additionalCompilerClasspath : null ); - - compilationCache.update( compilationRequest, resultHolder ); - return resultHolder; - } - - protected Object loadAndInstantiate(ClassLoader processorClassloader, Class clazz) { - try { - return processorClassloader.loadClass( clazz.getName() ).newInstance(); - } - catch ( Exception e ) { - throw new RuntimeException( e ); - } - } - - protected abstract CompilationOutcomeDescriptor compileWithSpecificCompiler( - CompilationRequest compilationRequest, - String sourceOutputDir, - String classOutputDir, - String additionalCompilerClasspath); - - boolean needsRecompilation() { - return !compilationRequest.equals( compilationCache.getLastRequest() ); - } - - private static String getBasePath() { - try { - return new File( "." ).getCanonicalPath(); - } - catch ( IOException e ) { - throw new RuntimeException( e ); - } - } - - private void createOutputDirs() { - File directory = new File( classOutputDir ); - deleteDirectory( directory ); - directory.mkdirs(); - - directory = new File( sourceOutputDir ); - deleteDirectory( directory ); - directory.mkdirs(); - - directory = new File( additionalCompilerClasspath ); - deleteDirectory( directory ); - directory.mkdirs(); - } - - private void deleteDirectory(File path) { - if ( path.exists() ) { - File[] files = path.listFiles(); - for ( int i = 0; i < files.length; i++ ) { - if ( files[i].isDirectory() ) { - deleteDirectory( files[i] ); - } - else { - files[i].delete(); - } - } - } - path.delete(); - } - - private boolean prepareServices() { - if ( !compilationRequest.getServices().isEmpty() ) { - String servicesDir = - additionalCompilerClasspath + File.separator + "META-INF" + File.separator + "services"; - File directory = new File( servicesDir ); - deleteDirectory( directory ); - directory.mkdirs(); - for ( Map.Entry, Class> serviceEntry : compilationRequest.getServices().entrySet() ) { - try { - File file = new File( servicesDir + File.separator + serviceEntry.getKey().getName() ); - FileWriter fileWriter = new FileWriter( file ); - fileWriter.append( serviceEntry.getValue().getName() ).append( "\n" ); - fileWriter.flush(); - fileWriter.close(); - } - catch ( IOException e ) { - throw new RuntimeException( e ); - } - } - - return true; - } - - return false; - } - - private static class DiagnosticDescriptorComparator implements Comparator { - - @Override - public int compare(DiagnosticDescriptor o1, DiagnosticDescriptor o2) { - String sourceFileName1 = o1.getSourceFileName() != null ? o1.getSourceFileName() : ""; - String sourceFileName2 = o2.getSourceFileName() != null ? o2.getSourceFileName() : ""; - - int result = sourceFileName1.compareTo( sourceFileName2 ); - - if ( result != 0 ) { - return result; - } - result = Long.valueOf( o1.getLine() ).compareTo( o2.getLine() ); - if ( result != 0 ) { - return result; - } - - return o1.getKind().compareTo( o2.getKind() ); - } - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/DisabledOnCompiler.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/DisabledOnCompiler.java deleted file mode 100644 index 41bcdb1000..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/DisabledOnCompiler.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * This should be used with care. - * This is similar to the JUnit 5 DisabledOnJre (once we have JUnit 5 we can replace this one) - * - * @author Filip Hrisafov - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface DisabledOnCompiler { - /** - * @return The compiler to use. - */ - Compiler value(); -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.java new file mode 100644 index 0000000000..6b13036be3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingExtension.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.ap.testutil.runner; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import javax.lang.model.SourceVersion; + +import org.codehaus.plexus.compiler.CompilerConfiguration; +import org.codehaus.plexus.compiler.CompilerException; +import org.codehaus.plexus.compiler.CompilerResult; +import org.codehaus.plexus.logging.console.ConsoleLogger; +import org.eclipse.tycho.compiler.jdt.JDTCompiler; +import org.mapstruct.ap.MappingProcessor; +import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; + +/** + * Extension that uses the Eclipse JDT compiler to compile. + * + * @author Andreas Gudian + * @author Filip Hrisafov + */ +class EclipseCompilingExtension extends CompilingExtension { + + private static final List ECLIPSE_COMPILER_CLASSPATH = buildEclipseCompilerClasspath(); + + private static final ClassLoader DEFAULT_ECLIPSE_COMPILER_CLASSLOADER = + new ModifiableURLClassLoader( newFilteringClassLoaderForEclipse() ) + .withPaths( ECLIPSE_COMPILER_CLASSPATH ) + .withPaths( PROCESSOR_CLASSPATH ) + .withOriginOf( ClassLoaderExecutor.class ); + + EclipseCompilingExtension() { + super( Compiler.ECLIPSE ); + } + + @Override + protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir, + String additionalCompilerClasspath) { + Collection processorClassPaths = getProcessorClasspathDependencies( + compilationRequest, + additionalCompilerClasspath + ); + ClassLoader compilerClassloader; + if ( processorClassPaths.isEmpty() ) { + compilerClassloader = DEFAULT_ECLIPSE_COMPILER_CLASSLOADER; + } + else { + ModifiableURLClassLoader loader = new ModifiableURLClassLoader( + newFilteringClassLoaderForEclipse() + .hidingClasses( compilationRequest.getServices().values() ) ); + + compilerClassloader = loader.withPaths( ECLIPSE_COMPILER_CLASSPATH ) + .withPaths( PROCESSOR_CLASSPATH ) + .withOriginOf( ClassLoaderExecutor.class ) + .withPaths( processorClassPaths ) + .withOriginsOf( compilationRequest.getServices().values() ); + } + + ClassLoaderHelper clHelper = + (ClassLoaderHelper) loadAndInstantiate( compilerClassloader, ClassLoaderExecutor.class ); + + return clHelper.compileInOtherClassloader( + compilationRequest, + getTestCompilationClasspath( compilationRequest, classOutputDir ), + getSourceFiles( compilationRequest.getSourceClasses() ), + SOURCE_DIR, + sourceOutputDir, + classOutputDir ); + } + + private static List getTestCompilationClasspath(CompilationRequest request, String classOutputDir) { + Collection testDependencies = request.getTestDependencies(); + Collection processorDependencies = request.getProcessorDependencies(); + Collection kotlinSources = request.getKotlinSources(); + if ( testDependencies.isEmpty() && processorDependencies.isEmpty() && kotlinSources.isEmpty() ) { + return TEST_COMPILATION_CLASSPATH; + } + + List testCompilationPaths = new ArrayList<>( + TEST_COMPILATION_CLASSPATH.size() + testDependencies.size() + processorDependencies.size() + 1 ); + + testCompilationPaths.addAll( TEST_COMPILATION_CLASSPATH ); + testCompilationPaths.addAll( filterBootClassPath( testDependencies ) ); + testCompilationPaths.addAll( filterBootClassPath( processorDependencies ) ); + if ( !kotlinSources.isEmpty() ) { + testCompilationPaths.add( classOutputDir ); + } + return testCompilationPaths; + } + + private static FilteringParentClassLoader newFilteringClassLoaderForEclipse() { + return new FilteringParentClassLoader( + // reload eclipse compiler classes + "org.eclipse.", + "kotlin.", + // reload mapstruct processor classes + "org.mapstruct.ap.internal.", + "org.mapstruct.ap.spi.", + "org.mapstruct.ap.MappingProcessor") + .hidingClass( ClassLoaderExecutor.class ); + } + + public interface ClassLoaderHelper { + CompilationOutcomeDescriptor compileInOtherClassloader(CompilationRequest compilationRequest, + List testCompilationClasspath, + Set sourceFiles, + String sourceDir, + String sourceOutputDir, + String classOutputDir); + } + + public static final class ClassLoaderExecutor implements ClassLoaderHelper { + @Override + public CompilationOutcomeDescriptor compileInOtherClassloader(CompilationRequest compilationRequest, + List testCompilationClasspath, + Set sourceFiles, + String sourceDir, + String sourceOutputDir, + String classOutputDir) { + JDTCompiler compiler = new JDTCompiler(); + compiler.enableLogging( new ConsoleLogger( 5, "JDT-Compiler" ) ); + + CompilerConfiguration config = new CompilerConfiguration(); + + config.setClasspathEntries( testCompilationClasspath ); + config.setOutputLocation( classOutputDir ); + config.setGeneratedSourcesDirectory( new File( sourceOutputDir ) ); + config.setAnnotationProcessors( new String[] { MappingProcessor.class.getName() } ); + config.setSourceFiles( sourceFiles ); + String version = getSourceVersion(); + config.setShowWarnings( false ); + config.setSourceVersion( version ); + config.setTargetVersion( version ); + + for ( String option : compilationRequest.getProcessorOptions() ) { + config.addCompilerCustomArgument( option, null ); + } + + CompilerResult compilerResult; + try { + compilerResult = compiler.performCompile( config ); + } + catch ( CompilerException e ) { + throw new RuntimeException( e ); + } + + return CompilationOutcomeDescriptor.forResult( + sourceDir, + compilerResult ); + } + + private static String getSourceVersion() { + SourceVersion latest = SourceVersion.latest(); + if ( latest == SourceVersion.RELEASE_8 ) { + return "1.8"; + } + return "11"; + } + + } + + private static List buildEclipseCompilerClasspath() { + Collection whitelist = Arrays.asList( + "tycho-compiler", + "ecj", + "plexus-compiler-api", + "plexus-component-annotations" + ); + + return filterBootClassPath( whitelist ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingStatement.java deleted file mode 100644 index a0617322ec..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EclipseCompilingStatement.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import java.io.File; -import java.util.List; -import java.util.Set; - -import org.codehaus.plexus.compiler.CompilerConfiguration; -import org.codehaus.plexus.compiler.CompilerException; -import org.codehaus.plexus.compiler.CompilerResult; -import org.codehaus.plexus.logging.console.ConsoleLogger; -import org.eclipse.tycho.compiler.jdt.JDTCompiler; -import org.junit.runners.model.FrameworkMethod; -import org.mapstruct.ap.MappingProcessor; -import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; - -/** - * Statement that uses the Eclipse JDT compiler to compile. - * - * @author Andreas Gudian - */ -class EclipseCompilingStatement extends CompilingStatement { - - private static final List ECLIPSE_COMPILER_CLASSPATH = buildEclipseCompilerClasspath(); - - private static final ClassLoader DEFAULT_ECLIPSE_COMPILER_CLASSLOADER = - new ModifiableURLClassLoader( newFilteringClassLoaderForEclipse() ) - .withPaths( ECLIPSE_COMPILER_CLASSPATH ) - .withPaths( PROCESSOR_CLASSPATH ) - .withOriginOf( ClassLoaderExecutor.class ); - - EclipseCompilingStatement(FrameworkMethod method, CompilationCache compilationCache) { - super( method, compilationCache ); - } - - @Override - protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRequest compilationRequest, - String sourceOutputDir, - String classOutputDir, - String additionalCompilerClasspath) { - ClassLoader compilerClassloader; - if ( additionalCompilerClasspath == null ) { - compilerClassloader = DEFAULT_ECLIPSE_COMPILER_CLASSLOADER; - } - else { - ModifiableURLClassLoader loader = new ModifiableURLClassLoader( - newFilteringClassLoaderForEclipse() - .hidingClasses( compilationRequest.getServices().values() ) ); - - compilerClassloader = loader.withPaths( ECLIPSE_COMPILER_CLASSPATH ) - .withPaths( PROCESSOR_CLASSPATH ) - .withOriginOf( ClassLoaderExecutor.class ) - .withPath( additionalCompilerClasspath ) - .withOriginsOf( compilationRequest.getServices().values() ); - } - - ClassLoaderHelper clHelper = - (ClassLoaderHelper) loadAndInstantiate( compilerClassloader, ClassLoaderExecutor.class ); - - return clHelper.compileInOtherClassloader( - compilationRequest, - TEST_COMPILATION_CLASSPATH, - getSourceFiles( compilationRequest.getSourceClasses() ), - SOURCE_DIR, - sourceOutputDir, - classOutputDir ); - } - - private static FilteringParentClassLoader newFilteringClassLoaderForEclipse() { - return new FilteringParentClassLoader( - // reload eclipse compiler classes - "org.eclipse.", - // reload mapstruct processor classes - "org.mapstruct.ap.internal.", - "org.mapstruct.ap.spi.", - "org.mapstruct.ap.MappingProcessor") - .hidingClass( ClassLoaderExecutor.class ); - } - - public interface ClassLoaderHelper { - CompilationOutcomeDescriptor compileInOtherClassloader(CompilationRequest compilationRequest, - List testCompilationClasspath, - Set sourceFiles, - String sourceDir, - String sourceOutputDir, - String classOutputDir); - } - - public static final class ClassLoaderExecutor implements ClassLoaderHelper { - @Override - public CompilationOutcomeDescriptor compileInOtherClassloader(CompilationRequest compilationRequest, - List testCompilationClasspath, - Set sourceFiles, - String sourceDir, - String sourceOutputDir, - String classOutputDir) { - JDTCompiler compiler = new JDTCompiler(); - compiler.enableLogging( new ConsoleLogger( 5, "JDT-Compiler" ) ); - - CompilerConfiguration config = new CompilerConfiguration(); - - config.setClasspathEntries( testCompilationClasspath ); - config.setOutputLocation( classOutputDir ); - config.setGeneratedSourcesDirectory( new File( sourceOutputDir ) ); - config.setAnnotationProcessors( new String[] { MappingProcessor.class.getName() } ); - config.setSourceFiles( sourceFiles ); - config.setShowWarnings( false ); - config.setSourceVersion( "1.8" ); - config.setTargetVersion( "1.8" ); - - for ( String option : compilationRequest.getProcessorOptions() ) { - config.addCompilerCustomArgument( option, null ); - } - - CompilerResult compilerResult; - try { - compilerResult = compiler.performCompile( config ); - } - catch ( CompilerException e ) { - throw new RuntimeException( e ); - } - - return CompilationOutcomeDescriptor.forResult( - sourceDir, - compilerResult ); - } - } - - private static List buildEclipseCompilerClasspath() { - String[] whitelist = - new String[] { - "tycho-compiler", - "org.eclipse.jdt.", - "plexus-compiler-api", - "plexus-component-annotations" }; - - return filterBootClassPath( whitelist ); - } - - @Override - protected String getPathSuffix() { - return "_eclipse"; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EnabledOnCompiler.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/EnabledOnCompiler.java deleted file mode 100644 index e297bd2c65..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/EnabledOnCompiler.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * This should be used with care. - * This is similar to the JUnit 5 EnabledOnJre (once we have JUnit 5 we can replace this one) - * - * @author Filip Hrisafov - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.RUNTIME) -public @interface EnabledOnCompiler { - /** - * @return The compiler to use. - */ - Compiler value(); -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/FilteringParentClassLoader.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/FilteringParentClassLoader.java index a38bc079da..c83b76c10b 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/FilteringParentClassLoader.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/FilteringParentClassLoader.java @@ -22,7 +22,12 @@ final class FilteringParentClassLoader extends ClassLoader { * @param excludedPrefixes class name prefixes to exclude */ FilteringParentClassLoader(String... excludedPrefixes) { - this.excludedPrefixes = new ArrayList( Arrays.asList( excludedPrefixes ) ); + this.excludedPrefixes = new ArrayList<>( Arrays.asList( excludedPrefixes ) ); + } + + FilteringParentClassLoader(ClassLoader parent, String... excludedPrefixes) { + super( parent ); + this.excludedPrefixes = new ArrayList<>( Arrays.asList( excludedPrefixes ) ); } /** diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java index 233a460bc4..6785ea5cf2 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/GeneratedSource.java @@ -6,56 +6,71 @@ package org.mapstruct.ap.testutil.runner; import java.io.File; -import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; import org.mapstruct.ap.testutil.assertions.JavaFileAssert; import static org.assertj.core.api.Assertions.fail; +import static org.mapstruct.ap.testutil.runner.CompilingExtension.NAMESPACE; /** - * A {@link TestRule} to perform assertions on generated source files. + * A {@link org.junit.jupiter.api.extension.RegisterExtension RegisterExtension} to perform assertions on generated + * source files. *

        * To add it to the test, use: * *

        - * @Rule
        - * public GeneratedSource generatedSources = new GeneratedSource();
        + * @RegisterExtension
        + * final GeneratedSource generatedSources = new GeneratedSource();
          * 
        * * @author Andreas Gudian */ -public class GeneratedSource implements TestRule { +public class GeneratedSource implements BeforeTestExecutionCallback, AfterTestExecutionCallback { private static final String FIXTURES_ROOT = "fixtures/"; /** - * static ThreadLocal, as the {@link CompilingStatement} must inject itself statically for this rule to gain access - * to the statement's information. As test execution of different classes in parallel is supported. + * ThreadLocal, as the source dir must be injected for this extension to gain access + * to the compilation information. As test execution of different classes in parallel is supported. */ - private static ThreadLocal compilingStatement = new ThreadLocal(); + private ThreadLocal sourceOutputDir = new ThreadLocal<>(); - private List> fixturesFor = new ArrayList>(); + private Compiler compiler; + + private List> fixturesFor = new ArrayList<>(); @Override - public Statement apply(Statement base, Description description) { - return new GeneratedSourceStatement( base ); + public void beforeTestExecution(ExtensionContext context) throws Exception { + CompilationRequest compilationRequest = context.getStore( NAMESPACE ) + .get( context.getUniqueId() + "-compilationRequest", CompilationRequest.class ); + this.compiler = compilationRequest.getCompiler(); + setSourceOutputDir( context.getStore( NAMESPACE ) + .get( compilationRequest, CompilationCache.class ) + .getLastSourceOutputDir() ); } - static void setCompilingStatement(CompilingStatement compilingStatement) { - GeneratedSource.compilingStatement.set( compilingStatement ); + @Override + public void afterTestExecution(ExtensionContext context) throws Exception { + handleFixtureComparison(); + clearSourceOutputDir(); } - static void clearCompilingStatement() { - GeneratedSource.compilingStatement.remove(); + private void setSourceOutputDir(String sourceOutputDir) { + this.sourceOutputDir.set( sourceOutputDir ); } + private void clearSourceOutputDir() { + this.sourceOutputDir.remove(); + } /** * Adds more mappers that need to be compared. @@ -67,9 +82,7 @@ static void clearCompilingStatement() { * @return the same rule for chaining */ public GeneratedSource addComparisonToFixtureFor(Class... fixturesFor) { - for ( Class fixture : fixturesFor ) { - this.fixturesFor.add( fixture ); - } + this.fixturesFor.addAll( Arrays.asList( fixturesFor ) ); return this; } @@ -103,40 +116,38 @@ public JavaFileAssert forDecoratedMapper(Class mapperClass) { * @return an assert for the file specified by the given path */ public JavaFileAssert forJavaFile(String path) { - return new JavaFileAssert( new File( compilingStatement.get().getSourceOutputDir() + "/" + path ) ); - } - - private class GeneratedSourceStatement extends Statement { - private final Statement next; - - private GeneratedSourceStatement(Statement next) { - this.next = next; - } - - @Override - public void evaluate() throws Throwable { - next.evaluate(); - handleFixtureComparison(); - } + return new JavaFileAssert( new File( sourceOutputDir.get() + "/" + path ) ); } - private void handleFixtureComparison() throws UnsupportedEncodingException { + private void handleFixtureComparison() { for ( Class fixture : fixturesFor ) { - String expectedFixture = FIXTURES_ROOT + getMapperName( fixture ); - URL expectedFile = getClass().getClassLoader().getResource( expectedFixture ); + String fixtureName = getMapperName( fixture ); + URL expectedFile = getExpectedResource( fixtureName ); if ( expectedFile == null ) { fail( String.format( "No reference file could be found for Mapper %s. You should create a file %s", fixture.getName(), - expectedFixture + FIXTURES_ROOT + fixtureName ) ); } else { - File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), "UTF-8" ) ); + File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), StandardCharsets.UTF_8 ) ); forMapper( fixture ).hasSameMapperContent( expectedResource ); } fixture.getPackage().getName(); } } + + private URL getExpectedResource( String fixtureName ) { + ClassLoader classLoader = getClass().getClassLoader(); + for ( int version = Runtime.version().feature(); version >= 11 && compiler != Compiler.ECLIPSE; version-- ) { + URL resource = classLoader.getResource( FIXTURES_ROOT + "/" + version + "/" + fixtureName ); + if ( resource != null ) { + return resource; + } + } + + return classLoader.getResource( FIXTURES_ROOT + fixtureName ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/InnerAnnotationProcessorRunner.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/InnerAnnotationProcessorRunner.java deleted file mode 100644 index 4850a48704..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/InnerAnnotationProcessorRunner.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.Statement; -import org.junit.runners.model.TestClass; - -/** - * Internal test runner that runs the tests of one class for one specific compiler implementation. - * - * @author Andreas Gudian - */ -class InnerAnnotationProcessorRunner extends BlockJUnit4ClassRunner { - static final ModifiableURLClassLoader TEST_CLASS_LOADER = new ModifiableURLClassLoader(); - private final Class klass; - private final Compiler compiler; - private final CompilationCache compilationCache; - private Class klassToUse; - private ReplacableTestClass replacableTestClass; - - /** - * @param klass the test class - * - * @throws Exception see {@link BlockJUnit4ClassRunner#BlockJUnit4ClassRunner(Class)} - */ - InnerAnnotationProcessorRunner(Class klass, Compiler compiler) throws Exception { - super( klass ); - this.klass = klass; - this.compiler = compiler; - this.compilationCache = new CompilationCache(); - } - - /** - * newly loads the class with the test class loader and sets that loader as context class loader of the thread - * - * @param klass the class to replace - * - * @return the class loaded with the test class loader - */ - private static Class replaceClassLoaderAndClass(Class klass) { - replaceContextClassLoader( klass ); - - try { - return Thread.currentThread().getContextClassLoader().loadClass( klass.getName() ); - } - catch ( ClassNotFoundException e ) { - throw new RuntimeException( e ); - } - - } - - private static void replaceContextClassLoader(Class klass) { - ModifiableURLClassLoader testClassLoader = new ModifiableURLClassLoader().withOriginOf( klass ); - - Thread.currentThread().setContextClassLoader( testClassLoader ); - } - - @Override - protected boolean isIgnored(FrameworkMethod child) { - return super.isIgnored( child ) || isIgnoredForCompiler( child ); - } - - protected boolean isIgnoredForCompiler(FrameworkMethod child) { - EnabledOnCompiler enabledOnCompiler = child.getAnnotation( EnabledOnCompiler.class ); - if ( enabledOnCompiler != null ) { - return enabledOnCompiler.value() != compiler; - } - - DisabledOnCompiler disabledOnCompiler = child.getAnnotation( DisabledOnCompiler.class ); - if ( disabledOnCompiler != null ) { - return disabledOnCompiler.value() == compiler; - } - - return false; - } - - @Override - protected TestClass createTestClass(final Class testClass) { - replacableTestClass = new ReplacableTestClass( testClass ); - return replacableTestClass; - } - - private FrameworkMethod replaceFrameworkMethod(FrameworkMethod m) { - try { - return new FrameworkMethod( - klassToUse.getDeclaredMethod( m.getName(), m.getMethod().getParameterTypes() ) ); - } - catch ( NoSuchMethodException e ) { - throw new RuntimeException( e ); - } - } - - @Override - protected Statement methodBlock(FrameworkMethod method) { - CompilingStatement statement = createCompilingStatement( method ); - if ( statement.needsRecompilation() ) { - klassToUse = replaceClassLoaderAndClass( klass ); - - replacableTestClass.replaceClass( klassToUse ); - } - - method = replaceFrameworkMethod( method ); - - Statement next = super.methodBlock( method ); - - statement.setNextStatement( next ); - - return statement; - } - - private CompilingStatement createCompilingStatement(FrameworkMethod method) { - if ( compiler == Compiler.JDK ) { - return new JdkCompilingStatement( method, compilationCache ); - } - else if ( compiler == Compiler.JDK11 ) { - return new Jdk11CompilingStatement( method, compilationCache ); - } - else { - return new EclipseCompilingStatement( method, compilationCache ); - } - } - - @Override - protected String getName() { - return "[" + compiler.name().toLowerCase() + "]"; - } - - @Override - protected String testName(FrameworkMethod method) { - return method.getName() + getName(); - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/Jdk11CompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/Jdk11CompilingStatement.java deleted file mode 100644 index 30f12aead7..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/Jdk11CompilingStatement.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import java.util.List; - -import org.junit.runners.model.FrameworkMethod; -import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor; - -/** - * Statement that uses the JDK compiler to compile. - * - * @author Filip Hrisafov - */ -class Jdk11CompilingStatement extends JdkCompilingStatement { - - Jdk11CompilingStatement(FrameworkMethod method, CompilationCache compilationCache) { - super( method, compilationCache ); - } - - - /** - * The JDK 11 compiler reports all ERROR diagnostics properly. Also when there are multiple per line. - */ - @Override - protected List filterExpectedDiagnostics(List expectedDiagnostics) { - return expectedDiagnostics; - } - - @Override - protected String getPathSuffix() { - return "_jdk"; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.java new file mode 100644 index 0000000000..1e9a1bc22f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingExtension.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.testutil.runner; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import javax.annotation.processing.Processor; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaCompiler.CompilationTask; +import javax.tools.JavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +import org.mapstruct.ap.MappingProcessor; +import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; + +/** + * Extension that uses the JDK compiler to compile. + * + * @author Andreas Gudian + * @author Filip Hrisafov + */ +class JdkCompilingExtension extends CompilingExtension { + + private static final List COMPILER_CLASSPATH_FILES = asFiles( TEST_COMPILATION_CLASSPATH ); + + private static final ClassLoader DEFAULT_PROCESSOR_CLASSLOADER = + new ModifiableURLClassLoader( newFilteringClassLoaderForJdk() ) + .withPaths( PROCESSOR_CLASSPATH ); + + JdkCompilingExtension() { + super( Compiler.JDK ); + } + + @Override + protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRequest compilationRequest, + String sourceOutputDir, + String classOutputDir, + String additionalCompilerClasspath) { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, StandardCharsets.UTF_8 ); + + Iterable compilationUnits = + fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.getSourceClasses() ) ); + + try { + fileManager.setLocation( + StandardLocation.CLASS_PATH, + getCompilerClasspathFiles( compilationRequest, classOutputDir ) + ); + fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) ); + fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) ); + } + catch ( IOException e ) { + throw new RuntimeException( e ); + } + + Collection processorClassPaths = getProcessorClasspathDependencies( + compilationRequest, + additionalCompilerClasspath + ); + ClassLoader processorClassloader; + if ( processorClassPaths.isEmpty() ) { + processorClassloader = DEFAULT_PROCESSOR_CLASSLOADER; + } + else { + processorClassloader = new ModifiableURLClassLoader( + newFilteringClassLoaderForJdk() + .hidingClasses( compilationRequest.getServices().values() ) + ) + .withPaths( PROCESSOR_CLASSPATH ) + .withPaths( processorClassPaths ) + .withOriginsOf( compilationRequest.getServices().values() ); + } + + CompilationTask task = + compiler.getTask( + null, + fileManager, + diagnostics, + compilationRequest.getProcessorOptions(), + null, + compilationUnits ); + + task.setProcessors( + Arrays.asList( (Processor) loadAndInstantiate( processorClassloader, MappingProcessor.class ) ) ); + + boolean compilationSuccessful = task.call(); + + return CompilationOutcomeDescriptor.forResult( + SOURCE_DIR, + compilationSuccessful, + diagnostics.getDiagnostics() ); + } + + private static List getCompilerClasspathFiles(CompilationRequest request, String classOutputDir) { + Collection testDependencies = request.getTestDependencies(); + Collection processorDependencies = request.getProcessorDependencies(); + Collection kotlinSources = request.getKotlinSources(); + if ( testDependencies.isEmpty() && processorDependencies.isEmpty() && kotlinSources.isEmpty() ) { + return COMPILER_CLASSPATH_FILES; + } + + List compilerClasspathFiles = new ArrayList<>( + COMPILER_CLASSPATH_FILES.size() + testDependencies.size() + processorDependencies.size() + 1 ); + + compilerClasspathFiles.addAll( COMPILER_CLASSPATH_FILES ); + for ( String testDependencyPath : filterBootClassPath( testDependencies ) ) { + compilerClasspathFiles.add( new File( testDependencyPath ) ); + } + + if ( !kotlinSources.isEmpty() ) { + compilerClasspathFiles.add( new File( classOutputDir ) ); + } + + return compilerClasspathFiles; + } + + private static List asFiles(List paths) { + List classpath = new ArrayList<>(); + for ( String path : paths ) { + classpath.add( new File( path ) ); + } + + return classpath; + } + + private static FilteringParentClassLoader newFilteringClassLoaderForJdk() { + return new FilteringParentClassLoader( + "kotlin.", + // reload mapstruct processor classes + "org.mapstruct.ap.internal.", + "org.mapstruct.ap.spi.", + "org.mapstruct.ap.MappingProcessor" + ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingStatement.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingStatement.java deleted file mode 100644 index 119ad42674..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/JdkCompilingStatement.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.annotation.processing.Processor; -import javax.tools.Diagnostic.Kind; -import javax.tools.DiagnosticCollector; -import javax.tools.JavaCompiler; -import javax.tools.JavaCompiler.CompilationTask; -import javax.tools.JavaFileObject; -import javax.tools.StandardJavaFileManager; -import javax.tools.StandardLocation; -import javax.tools.ToolProvider; - -import org.junit.runners.model.FrameworkMethod; -import org.mapstruct.ap.MappingProcessor; -import org.mapstruct.ap.testutil.compilation.model.CompilationOutcomeDescriptor; -import org.mapstruct.ap.testutil.compilation.model.DiagnosticDescriptor; - -/** - * Statement that uses the JDK compiler to compile. - * - * @author Andreas Gudian - */ -class JdkCompilingStatement extends CompilingStatement { - - private static final List COMPILER_CLASSPATH_FILES = asFiles( TEST_COMPILATION_CLASSPATH ); - - private static final ClassLoader DEFAULT_PROCESSOR_CLASSLOADER = - new ModifiableURLClassLoader( new FilteringParentClassLoader( "org.mapstruct." ) ) - .withPaths( PROCESSOR_CLASSPATH ); - - JdkCompilingStatement(FrameworkMethod method, CompilationCache compilationCache) { - super( method, compilationCache ); - } - - @Override - protected CompilationOutcomeDescriptor compileWithSpecificCompiler(CompilationRequest compilationRequest, - String sourceOutputDir, - String classOutputDir, - String additionalCompilerClasspath) { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - DiagnosticCollector diagnostics = new DiagnosticCollector(); - StandardJavaFileManager fileManager = compiler.getStandardFileManager( null, null, null ); - - Iterable compilationUnits = - fileManager.getJavaFileObjectsFromFiles( getSourceFiles( compilationRequest.getSourceClasses() ) ); - - try { - fileManager.setLocation( StandardLocation.CLASS_PATH, COMPILER_CLASSPATH_FILES ); - fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( new File( classOutputDir ) ) ); - fileManager.setLocation( StandardLocation.SOURCE_OUTPUT, Arrays.asList( new File( sourceOutputDir ) ) ); - } - catch ( IOException e ) { - throw new RuntimeException( e ); - } - - ClassLoader processorClassloader; - if ( additionalCompilerClasspath == null ) { - processorClassloader = DEFAULT_PROCESSOR_CLASSLOADER; - } - else { - processorClassloader = new ModifiableURLClassLoader( - new FilteringParentClassLoader( "org.mapstruct." ) ) - .withPaths( PROCESSOR_CLASSPATH ) - .withPath( additionalCompilerClasspath ) - .withOriginsOf( compilationRequest.getServices().values() ); - } - - CompilationTask task = - compiler.getTask( - null, - fileManager, - diagnostics, - compilationRequest.getProcessorOptions(), - null, - compilationUnits ); - - task.setProcessors( - Arrays.asList( (Processor) loadAndInstantiate( processorClassloader, MappingProcessor.class ) ) ); - - boolean compilationSuccessful = task.call(); - - return CompilationOutcomeDescriptor.forResult( - SOURCE_DIR, - compilationSuccessful, - diagnostics.getDiagnostics() ); - } - - private static List asFiles(List paths) { - List classpath = new ArrayList(); - for ( String path : paths ) { - classpath.add( new File( path ) ); - } - - return classpath; - } - - /** - * The JDK compiler only reports the first message of kind ERROR that is reported for one source file line, so we - * filter out the surplus diagnostics. The input list is already sorted by file name and line number, with the order - * for the diagnostics in the same line being kept at the order as given in the test. - */ - @Override - protected List filterExpectedDiagnostics(List expectedDiagnostics) { - List filtered = new ArrayList( expectedDiagnostics.size() ); - - DiagnosticDescriptor previous = null; - for ( DiagnosticDescriptor diag : expectedDiagnostics ) { - if ( diag.getKind() != Kind.ERROR - || previous == null - || !previous.getSourceFileName().equals( diag.getSourceFileName() ) - || !previous.getLine().equals( diag.getLine() ) ) { - filtered.add( diag ); - previous = diag; - } - } - - return filtered; - } - - @Override - protected String getPathSuffix() { - return "_jdk"; - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java index afb7969630..2dd5d5b8e7 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ModifiableURLClassLoader.java @@ -31,7 +31,7 @@ final class ModifiableURLClassLoader extends URLClassLoader { tryRegisterAsParallelCapable(); } - private final ConcurrentMap addedURLs = new ConcurrentHashMap(); + private final ConcurrentMap addedURLs = new ConcurrentHashMap<>(); ModifiableURLClassLoader(ClassLoader parent) { super( new URL[] { }, parent ); @@ -99,19 +99,8 @@ private static void tryRegisterAsParallelCapable() { try { ClassLoader.class.getMethod( "registerAsParallelCapable" ).invoke( null ); } - catch ( NoSuchMethodException e ) { - return; // ignore - } - catch ( SecurityException e ) { - return; // ignore - } - catch ( IllegalAccessException e ) { - return; // ignore - } - catch ( IllegalArgumentException e ) { - return; // ignore - } - catch ( InvocationTargetException e ) { + catch ( NoSuchMethodException | SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e ) { return; // ignore } } diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ProcessorTestExtension.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ProcessorTestExtension.java new file mode 100644 index 0000000000..ee9e2733cc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ProcessorTestExtension.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.testutil.runner; + +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; +import org.mapstruct.ap.testutil.ProcessorTest; + +/** + * The provider of the processor tests based on the defined compilers. + * + * @author Filip Hrisafov + */ +public class ProcessorTestExtension 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 CompilerTest on " + testMethod ) ); + + return Stream.of( processorTest.value() ) + .map( ProcessorTestInvocationContext::new ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ProcessorTestInvocationContext.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ProcessorTestInvocationContext.java new file mode 100644 index 0000000000..d557095dff --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ProcessorTestInvocationContext.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.testutil.runner; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.TestTemplateInvocationContext; + +/** + * The template invocation processor responsible for providing the appropriate extensions for the different compilers. + * + * @author Filip Hrisafov + */ +public class ProcessorTestInvocationContext implements TestTemplateInvocationContext { + + protected Compiler compiler; + + public ProcessorTestInvocationContext(Compiler compiler) { + this.compiler = compiler; + } + + @Override + public String getDisplayName(int invocationIndex) { + return "[" + compiler.name().toLowerCase() + "]"; + } + + @Override + public List getAdditionalExtensions() { + List extensions = new ArrayList<>(); + extensions.add( new CompilerTestEnabledOnJreCondition( compiler ) ); + if ( compiler == Compiler.JDK ) { + extensions.add( new JdkCompilingExtension() ); + } + else if ( compiler == Compiler.ECLIPSE ) { + extensions.add( new EclipseCompilingExtension() ); + } + else { + throw new IllegalArgumentException( "Compiler [" + compiler + "] is not known" ); + } + + return extensions; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ReplacableTestClass.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/ReplacableTestClass.java deleted file mode 100644 index 7141b78146..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/ReplacableTestClass.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; -import java.util.List; - -import org.junit.runners.model.FrameworkField; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.TestClass; - -/** - * A {@link TestClass} where the wrapped Class can be replaced - * - * @author Andreas Gudian - */ -class ReplacableTestClass extends TestClass { - private TestClass delegate; - - /** - * @see TestClass#TestClass(Class) - */ - ReplacableTestClass(Class clazz) { - super( clazz ); - } - - /** - * @param clazz the new class - */ - void replaceClass(Class clazz) { - delegate = new TestClass( clazz ); - } - - @Override - public List getAnnotatedMethods() { - if ( null == delegate ) { - return super.getAnnotatedMethods(); - } - else { - return delegate.getAnnotatedMethods(); - } - } - - @Override - public List getAnnotatedMethods(Class annotationClass) { - if ( null == delegate ) { - return super.getAnnotatedMethods( annotationClass ); - } - else { - return delegate.getAnnotatedMethods( annotationClass ); - } - } - - @Override - public List getAnnotatedFields() { - if ( null == delegate ) { - return super.getAnnotatedFields(); - } - else { - return delegate.getAnnotatedFields(); - } - } - - @Override - public List getAnnotatedFields(Class annotationClass) { - if ( null == delegate ) { - return super.getAnnotatedFields( annotationClass ); - } - else { - return delegate.getAnnotatedFields( annotationClass ); - } - } - - @Override - public Class getJavaClass() { - if ( null == delegate ) { - return super.getJavaClass(); - } - else { - return delegate.getJavaClass(); - } - } - - @Override - public String getName() { - if ( null == delegate ) { - return super.getName(); - } - else { - return delegate.getName(); - } - } - - @Override - public Constructor getOnlyConstructor() { - if ( null == delegate ) { - return super.getOnlyConstructor(); - } - else { - return delegate.getOnlyConstructor(); - } - } - - @Override - public Annotation[] getAnnotations() { - if ( null == delegate ) { - return super.getAnnotations(); - } - else { - return delegate.getAnnotations(); - } - } - - @Override - public T getAnnotation(Class annotationType) { - if ( null == delegate ) { - return super.getAnnotation( annotationType ); - } - else { - return delegate.getAnnotation( annotationType ); - } - } - - @Override - public List getAnnotatedFieldValues(Object test, Class annotationClass, - Class valueClass) { - if ( null == delegate ) { - return super.getAnnotatedFieldValues( test, annotationClass, valueClass ); - } - else { - return delegate.getAnnotatedFieldValues( test, annotationClass, valueClass ); - } - } - - @Override - public List getAnnotatedMethodValues(Object test, Class annotationClass, - Class valueClass) { - if ( null == delegate ) { - return super.getAnnotatedMethodValues( test, annotationClass, valueClass ); - } - else { - return delegate.getAnnotatedMethodValues( test, annotationClass, valueClass ); - } - } - - @Override - public String toString() { - if ( null == delegate ) { - return super.toString(); - } - else { - return delegate.toString(); - } - } - - @Override - public boolean isPublic() { - if ( null == delegate ) { - return super.isPublic(); - } - else { - return delegate.isPublic(); - } - } - - @Override - public boolean isANonStaticInnerClass() { - if ( null == delegate ) { - return super.isANonStaticInnerClass(); - } - else { - return delegate.isANonStaticInnerClass(); - } - } - - @Override - public int hashCode() { - if ( null == delegate ) { - return super.hashCode(); - } - else { - return delegate.hashCode(); - } - } - - @Override - public boolean equals(Object obj) { - if ( null == delegate ) { - return super.equals( obj ); - } - else { - return delegate.equals( obj ); - } - } -} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/WithSingleCompiler.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/WithSingleCompiler.java deleted file mode 100644 index dc930f370a..0000000000 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/WithSingleCompiler.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright MapStruct Authors. - * - * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 - */ -package org.mapstruct.ap.testutil.runner; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Temporarily only use the specified compiler for the test during debugging / implementation. - *

        - * Do not commit tests with this annotation present! - * - * @deprecated Do not commit tests with this annotation present. Tests are expected to work with all compilers. - * @author Andreas Gudian - */ -@Deprecated -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface WithSingleCompiler { - /** - * @return The compiler to use. - */ - Compiler value(); -} diff --git a/processor/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener b/processor/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener new file mode 100644 index 0000000000..8ffa996976 --- /dev/null +++ b/processor/src/test/resources/META-INF/services/org.junit.platform.launcher.LauncherDiscoveryListener @@ -0,0 +1 @@ +org.mapstruct.ap.testutil.runner.CompilerLauncherDiscoveryListener \ No newline at end of file diff --git a/processor/src/test/resources/checkstyle-for-generated-sources.xml b/processor/src/test/resources/checkstyle-for-generated-sources.xml index eecb42111a..295fbeeffa 100644 --- a/processor/src/test/resources/checkstyle-for-generated-sources.xml +++ b/processor/src/test/resources/checkstyle-for-generated-sources.xml @@ -7,6 +7,8 @@ + +