diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..f6faee6938 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1a15fdec4a..0c6333a8ff 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -26,20 +26,20 @@ jobs: build-mode: autobuild steps: - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Set up Java if: matrix.language == 'java-kotlin' - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: 'zulu' java-version: '21' cache: 'maven' - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + uses: github/codeql-action/analyze@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/java-ea.yml b/.github/workflows/java-ea.yml index 2937606d81..04897b2156 100644 --- a/.github/workflows/java-ea.yml +++ b/.github/workflows/java-ea.yml @@ -14,9 +14,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: 'Set up JDK' - uses: oracle-actions/setup-java@v1 + uses: oracle-actions/setup-java@fff43251af9936a0e6a4d5d0946e14f1680e9b6b # v1.5.0 with: website: jdk.java.net release: EA diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 7e218ad351..865820f6ab 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -13,9 +13,9 @@ jobs: name: 'Mac OS' runs-on: macos-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: 'Set up JDK 21' - uses: actions/setup-java@v4 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: 'zulu' java-version: 21 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 168cb9c7f0..d1ebef4642 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,9 +20,9 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: 'Set up JDK' - uses: actions/setup-java@v4 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: 'zulu' java-version: ${{ matrix.java }} @@ -33,7 +33,7 @@ jobs: run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' if: matrix.java == 21 && github.repository == 'mapstruct/mapstruct' - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} @@ -49,16 +49,16 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: 'Set up JDK 21 for building everything' - uses: actions/setup-java@v4 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: 'zulu' java-version: 21 - name: 'Install Processor' run: ./mvnw ${MAVEN_ARGS} -DskipTests install -pl processor -am - name: 'Set up JDK ${{ matrix.java }} for running integration tests' - uses: actions/setup-java@v4 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: 'zulu' java-version: ${{ matrix.java }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index aafb79a534..58440dcebd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,13 +16,14 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + issues: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 - name: Setup Java - uses: actions/setup-java@v4 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: java-version: 21 distribution: 'zulu' @@ -69,7 +70,7 @@ jobs: - name: JReleaser output if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: jreleaser-release path: | @@ -98,7 +99,7 @@ jobs: contents: read steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: mapstruct/mapstruct.org ref: main diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d8d4de4263..0c6ccc1a25 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -13,9 +13,9 @@ jobs: name: 'Windows' runs-on: windows-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: 'Set up JDK 21' - uses: actions/setup-java@v4 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: 'zulu' java-version: 21 diff --git a/.gitignore b/.gitignore index a33f0014ee..3cfac4e9f5 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,7 @@ test-output .DS_Store checkstyle.cache .flattened-pom.xml + +.claude/settings.local.json +.claude/worktrees +.mcp.json diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 8dea6c227c..216df05897 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1,3 +1,3 @@ wrapperVersion=3.3.4 distributionType=only-script -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/NEXT_RELEASE_CHANGELOG.md b/NEXT_RELEASE_CHANGELOG.md index 8392b63c60..e0f4cd31f0 100644 --- a/NEXT_RELEASE_CHANGELOG.md +++ b/NEXT_RELEASE_CHANGELOG.md @@ -1,58 +1,10 @@ ### Features -* Add support for JSpecify nullness annotations (#1243) - MapStruct now detects `@NonNull`, `@Nullable`, `@NullMarked` and `@NullUnmarked` from `org.jspecify.annotations` to control null check generation: - - Source `@NonNull` skips null checks; target `@NonNull` always adds them - - `@NonNull` source parameters skip the method-level null guard - - `@NullMarked` / `@NullUnmarked` scope is resolved by walking method → class → outer class → package - - Compile error when mapping a potentially nullable source to a `@NonNull` constructor parameter without a `defaultValue` - - Can be disabled with the `mapstruct.disableJSpecify` compiler option -* Add `URI` to `String` built-in conversions (#4018) - ### Enhancements -* Support `SET_TO_NULL` for overloaded target methods, requiring a cast (#3949) -* Use multi-catch in generated code (#4021) -* Use diamond operator for `new` expressions in generated code (#4045) -* Always use factory methods for `LinkedHashMap` and `LinkedHashSet` when targeting `SequencedSet` and `SequencedMap` (#3990) -* Improve annotation processing performance by removing regex matching from `Type.describe()` (#3991) -* Add support for generics in arrays (#4050) - ### Bugs -* Prevent mapper generation from a type with a generic super bound to a type with a generic extends bound (#3994) -* Fix location for Javadoc when generating the distribution zip - ### Documentation -* Add `SECURITY.md` and `.github/INCIDENT_RESPONSE.md` - ### Build -* Test on JDK 25 and 26, drop integration test on JDK 11 (#4039) -* Specify OpenJDK 21 for the Jitpack build (#4042) -* Add CodeQL custom workflow and set build mode for `java-kotlin` to `none` -* Use diamond operator in test code (#4048) -* Upgrade integration tests to JUnit 5 (#4023) -* Update Maven compiler plugin (#3972) -* Upgrade Freemarker to 2.3.34 -* Update license plugin -* Enforce import order via Checkstyle `CustomImportOrder` (#4024) -* Enforce spaces inside parentheses for control flow statements via Checkstyle -* Simplify `fail` in `assertCheckstyleRules` -* Remove deprecated `Number` API usage from tests -* Use `StandardCharsets.UTF_8` in tests -* Remove obsolete override of AssertJ version in integration tests -* Let GitHub determine whether or not the released version is the latest -* Simplify parallel-capable registration in `ModifiableURLClassLoader` test util (#4052) -* Improve class loading for tests by replacing `LauncherDiscoveryListener` with `CompilerLauncherInterceptor` (#4051) - -### Internal - -* Upgrade internal `Visitor6` usages to `Visitor8` -* Refactor `TypeFactory.getTypeParameters` (#4020) -* Simplify boolean logic in `ValueMappingMethod` by removing inversion (#4007) -* Remove unnecessary `keySet()` invocation (#3989) -* Remove unused methods in `Fields` (#4010) -* Add missing self reference in `GeneratedTypeBuilder` (#4009) -* Fix self check in `equals` of `Type` (#3995) -* Remove reflection from `isDefaultMethod` (#4053) diff --git a/build-config/src/main/resources/build-config/checkstyle.xml b/build-config/src/main/resources/build-config/checkstyle.xml index fa83d1d709..b15e108c5c 100644 --- a/build-config/src/main/resources/build-config/checkstyle.xml +++ b/build-config/src/main/resources/build-config/checkstyle.xml @@ -149,7 +149,7 @@ - + diff --git a/copyright.txt b/copyright.txt index 39a863e531..74f18d89b1 100644 --- a/copyright.txt +++ b/copyright.txt @@ -8,6 +8,7 @@ Andreas Gudian - https://github.com/agudian Andrei Arlou - https://github.com/Captain1653 Andres Jose Sebastian Rincon Gonzalez - https://github.com/stianrincon Arne Seime - https://github.com/seime +Burak Yildirim - https://github.com/bydrim Cause Chung - https://github.com/cuzfrog Christian Bandowski - https://github.com/chris922 Chris DeLashmutt - https://github.com/cdelashmutt-pivotal @@ -36,6 +37,7 @@ Ivo Smid - https://github.com/bedla Jason Bodnar - https://github.com/Blackbaud-JasonBodnar Jeroen van Wilgenburg - https://github.com/jvwilge Jeff Smyth - https://github.com/smythie86 +jmwbRyDWLeNsvtzrihGoY - https://github.com/jmwbRyDWLeNsvtzrihGoY João Paulo Bassinello - https://github.com/jpbassinello Jonathan Kraska - https://github.com/jakraska Joshua Spoerri - https://github.com/spoerri @@ -57,6 +59,7 @@ Paul Strugnell - https://github.com/ps-powa Pascal Grün - https://github.com/pascalgn Pavel Makhov - https://github.com/streetturtle Peter Larson - https://github.com/pjlarson +Raimund Klein - https://github.com/Chessray Remko Plantenga - https://github.com/sonata82 Remo Meier - https://github.com/remmeier Roel Mangelschots - https://github.com/rmschots @@ -69,8 +72,10 @@ Samuel Wright - https://github.com/samwright Sebastian Haberey - https://github.com/sebastianhaberey Sebastian Hasait - https://github.com/shasait Sean Huang - https://github.com/seanjob +seonwoojung - https://github.com/seonwooj0810 Sjaak Derksen - https://github.com/sjaakd Stefan May - https://github.com/osthus-sm +Takch02 - https://github.com/Takch02 Taras Mychaskiw - https://github.com/twentylemon Thibault Duperron - https://github.com/Zomzog Tomáš Poledný - https://github.com/Saljack @@ -84,6 +89,7 @@ Vincent Alexander Beelte - https://github.com/grandmasterpixel Winter Andreas - https://github.dev/wandi34 Xiu Hong Kooi - https://github.com/kooixh Yang Tang - https://github.com/tangyang9464 +Yevhen Vasyliev - https://github.com/yvasyliev Zegveld - https://github.com/Zegveld znight1020 - https://github.com/znight1020 zral - https://github.com/zyberzebra diff --git a/core/src/main/java/org/mapstruct/ClassAccessibility.java b/core/src/main/java/org/mapstruct/ClassAccessibility.java new file mode 100644 index 0000000000..c533b1ea32 --- /dev/null +++ b/core/src/main/java/org/mapstruct/ClassAccessibility.java @@ -0,0 +1,28 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +/** + * Determines whether a generated Mapper implementation class will be declared {@code public} or not. + * + * @author Raimund Klein + * + * @since 1.7.0 + */ +public enum ClassAccessibility { + /** + * The generated Mapper will have the same modifier ({@code public} or none) as the annotated class or interface. + */ + DEFAULT, + /** + * The generated Mapper will be declared {@code public}. + */ + PUBLIC, + /** + * The generated Mapper will have no visibility modifier ("package-private"). + */ + PACKAGE_PRIVATE +} diff --git a/core/src/main/java/org/mapstruct/Mapper.java b/core/src/main/java/org/mapstruct/Mapper.java index 398dc1870a..c502dfe8a9 100644 --- a/core/src/main/java/org/mapstruct/Mapper.java +++ b/core/src/main/java/org/mapstruct/Mapper.java @@ -14,6 +14,7 @@ import org.mapstruct.control.MappingControl; import org.mapstruct.factory.Mappers; +import static org.mapstruct.ClassAccessibility.DEFAULT; import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR; @@ -388,4 +389,13 @@ NullValuePropertyMappingStrategy nullValuePropertyMappingStrategy() default * @since 1.5 */ boolean suppressTimestampInGenerated() default false; + + /** + * Determines the {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation. Default is to mirror the interface or abstract class annotated by this {@code Mapper}. + * + * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation + */ + ClassAccessibility accessibility() default DEFAULT; } diff --git a/core/src/main/java/org/mapstruct/MapperConfig.java b/core/src/main/java/org/mapstruct/MapperConfig.java index 8631562a56..a81e3e8d4f 100644 --- a/core/src/main/java/org/mapstruct/MapperConfig.java +++ b/core/src/main/java/org/mapstruct/MapperConfig.java @@ -14,6 +14,7 @@ import org.mapstruct.control.MappingControl; import org.mapstruct.factory.Mappers; +import static org.mapstruct.ClassAccessibility.DEFAULT; import static org.mapstruct.NullValueCheckStrategy.ON_IMPLICIT_CONVERSION; import static org.mapstruct.SubclassExhaustiveStrategy.COMPILE_ERROR; @@ -357,5 +358,14 @@ MappingInheritanceStrategy mappingInheritanceStrategy() * @since 1.5 */ boolean suppressTimestampInGenerated() default false; + + /** + * Determines the {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation. Default is to mirror the interface or abstract class annotated by {@code Mapper}. + * + * @return The {@link ClassAccessibility} ({@code public} or package-private) for the generated Mapper + * implementation + */ + ClassAccessibility accessibility() default DEFAULT; } diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 4c8be52503..306169193d 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -328,6 +328,11 @@ The existing safety guards (`defaultValue` / `defaultExpression`, primitive unbo ==== Method-level source parameter When the source parameter of a mapping method is annotated `@NonNull` (directly or via a `@NullMarked` scope), MapStruct skips the method-level null guard, since the caller is contractually obliged to pass a non-null value. +This rule applies uniformly to bean, iterable, map, and stream mapping methods. + +==== Method-level return type + +When the return type of a mapping method is `@NonNull` (directly or via a `@NullMarked` scope), MapStruct forces `NullValueMappingStrategy.RETURN_DEFAULT` semantics. The generated method returns a default-constructed target (bean methods), an empty collection (`Iterable` / array mappings), an empty map (`Map` mappings), or `Stream.empty()` (stream mappings) rather than `null`, so the return contract is never violated. This rule applies regardless of the explicit `NullValueMappingStrategy` setting. ==== Constructor parameter constraint diff --git a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc index 35afc93a51..ec817e0cdf 100644 --- a/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc +++ b/documentation/src/main/asciidoc/chapter-4-retrieving-a-mapper.asciidoc @@ -130,4 +130,24 @@ In that case utilize the `InjectionStrategy#SETTER` strategy. [TIP] ==== For abstract classes or decorators setter injection should be used. -==== \ No newline at end of file +==== + +[[class-accessibility]] +=== Class accessibility + +By default, the generated Mapper implementation will have the same accessibility (`public` or none) as the interface or abstract class carrying the `@Mapper` annotation. Especially in conjunction with a DI framework, this might not be desired. If you wish to reduce the visibility of the generated class, you can make use of the `accessibility` attribute like this: + +==== +[source, java, linenums] +[subs="verbatim,attributes"] +---- +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, accessibility = ClassAccessibility.PACKAGE_PRIVATE) +public interface CarMapper { + + CarDto carToCarDto(Car car); +} + +---- +==== + +In this case, the generated Mapper will have no modifier. Possible values are `ClassAccessibility.DEFAULT` (the behavior described above), `ClassAccessibility.PUBLIC`, and `ClassAccessibility.PACKAGE_PRIVATE` (no modifier). diff --git a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc index d95e6c7a62..10c41506de 100644 --- a/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc +++ b/documentation/src/main/asciidoc/chapter-5-data-type-conversions.asciidoc @@ -109,6 +109,14 @@ public interface CarMapper { * Between `java.time.LocalDateTime` from Java 8 Date-Time package and `java.time.LocalDate` from the same package. +* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.time.LocalDateTime` from the same package. + +* Between `java.time.OffsetDateTime` from Java 8 Date-Time package and `java.time.LocalDateTime` from the same package. + +* Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.time.Instant` from the same package. + +* Between `java.time.OffsetDateTime` from Java 8 Date-Time package and `java.time.Instant` from the same package. + * Between `java.time.ZonedDateTime` from Java 8 Date-Time package and `java.util.Calendar`. * Between `java.sql.Date` and `java.util.Date` diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java index 60784d206a..042d6ec1da 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/MavenIntegrationTest.java @@ -89,7 +89,7 @@ void jsr330Test() { @ProcessorTest(baseDir = "lombokBuilderTest", processorTypes = { ProcessorTest.ProcessorType.JAVAC }) - @DisabledOnJre(versions = 27) + @DisabledOnJre(versions = { 27, 28 }) void lombokBuilderTest() { } @@ -98,7 +98,7 @@ void lombokBuilderTest() { ProcessorTest.ProcessorType.JAVAC_WITH_PATHS }) @EnabledForJreRange(min = JRE.JAVA_11) - @DisabledOnJre(versions = 27) + @DisabledOnJre(versions = { 27, 28 }) void lombokModuleTest() { } diff --git a/parent/pom.xml b/parent/pom.xml index 1f340ba2ab..b444c1847a 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -39,7 +39,7 @@ 13.0.0 5.14.1 2.2.0 - 1.12.0 + 1.24.0 1 3.27.7 @@ -879,7 +879,10 @@ ALWAYS - true + + ALWAYS + true + false @@ -910,6 +913,9 @@ ${maven.multiModuleProjectDirectory}/NEXT_RELEASE_CHANGELOG.md + + \d+\.\d+\.\d+\.(Alpha|Beta|M|RC)\d* + <#assign needVarDefine = (beforeMappingReferencesWithMappingTarget?has_content || afterMappingReferences?has_content) && !existingInstanceMapping /> 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 104fd98437..224b6c3dfa 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 @@ -21,15 +21,35 @@ <#if nullTarget.targetAsException>throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>return <@writeTarget target=nullTarget.target/>; } - <@includeModel object=resultType/> ${resultName}; + <#if versionInformation.isSourceVersionAtLeast14()> + <#if valueMappings.empty> + <#if defaultTarget.targetAsException> + <@includeModel object=resultType/> ${resultName}; + throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} ); + <#else> + <@includeModel object=resultType/> ${resultName} = <@writeTarget target=defaultTarget.target/>; + + <#else> + <@includeModel object=resultType/> ${resultName} = switch ( ${sourceParameter.name} ) { + <#list valueMappings as valueMapping> + case <@writeSource source=valueMapping.source/> -> <#if valueMapping.targetAsException > throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else><@writeTarget target=valueMapping.target/>; + + <#if isDefaultTargetRequired()> + default -> <#if defaultTarget.targetAsException>throw new <@includeModel object=unexpectedValueMappingException/>( "Unexpected enum constant: " + ${sourceParameter.name} )<#else><@writeTarget target=defaultTarget.target/>; + + }; + + <#else> + <@includeModel object=resultType/> ${resultName}; - switch ( ${sourceParameter.name} ) { - <#list valueMappings as valueMapping> - case <@writeSource source=valueMapping.source/>: <#if valueMapping.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>${resultName} = <@writeTarget target=valueMapping.target/>; - break; - - default: <#if defaultTarget.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <@writeTarget target=defaultTarget.target/>; - } + switch ( ${sourceParameter.name} ) { + <#list valueMappings as valueMapping> + case <@writeSource source=valueMapping.source/>: <#if valueMapping.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} );<#else>${resultName} = <@writeTarget target=valueMapping.target/>; + break; + + default: <#if defaultTarget.targetAsException >throw new <@includeModel object=unexpectedValueMappingException />( "Unexpected enum constant: " + ${sourceParameter.name} )<#else>${resultName} = <@writeTarget target=defaultTarget.target/>; + } + <#list beforeMappingReferencesWithMappingTarget as callback> <#if callback_index = 0> diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Mapper.java new file mode 100644 index 0000000000..ab053bc44e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Mapper.java @@ -0,0 +1,93 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3948; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper +public interface Issue3948Mapper { + + class ComparableField implements Comparable { + + private final String code; + + public ComparableField(String code) { + this.code = code; + } + + @Override + public int compareTo(ComparableField o) { + return code.compareTo( o.code ); + } + } + + class Id implements Comparable> { + private final long value; + + public Id(long value) { + this.value = value; + } + + @Override + public int compareTo(Id o) { + return Long.compare( value, o.value ); + } + } + + class From { + private long id; + private String code; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + } + + class To { + private Id id; + private ComparableField comparableField; + + public Id getId() { + return id; + } + + public void setId(Id id) { + this.id = id; + } + + public ComparableField getComparableField() { + return comparableField; + } + + public void setComparableField(ComparableField comparableField) { + this.comparableField = comparableField; + } + } + + @Mapping(source = "code", target = "comparableField") + To convert(From from); + + default Id longToId(Long id) { + return id == null ? null : new Id<>(id); + } + + default ComparableField convert(String code) { + return code == null ? null : new ComparableField(code); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Test.java new file mode 100644 index 0000000000..8c80b2e802 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3948/Issue3948Test.java @@ -0,0 +1,23 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._3948; + +import org.junitpioneer.jupiter.Issue; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +/** + * @author hduelme + */ +@Issue("3948") +public class Issue3948Test { + + @ProcessorTest + @WithClasses(Issue3948Mapper.class) + public void shouldRequireExactGenericMatchForDeclaredSameTypes() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/ErroneousSetToDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/ErroneousSetToDefaultMapper.java new file mode 100644 index 0000000000..4ede382781 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/ErroneousSetToDefaultMapper.java @@ -0,0 +1,100 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._4060; + +import java.math.BigDecimal; +import java.time.LocalDate; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; + +@Mapper +public interface ErroneousSetToDefaultMapper { + + class WithLocalDate { + private LocalDate value; + + public LocalDate getValue() { + return value; + } + + public void setValue(LocalDate value) { + this.value = value; + } + } + + class WithBigDecimal { + private BigDecimal value; + + public BigDecimal getValue() { + return value; + } + + public void setValue(BigDecimal value) { + this.value = value; + } + } + + class WithComparable { + private Comparable value; + + public Comparable getValue() { + return value; + } + + public void setValue(Comparable value) { + this.value = value; + } + } + + abstract class AbstractValue { + } + + class WithAbstractValue { + private AbstractValue value; + + public AbstractValue getValue() { + return value; + } + + public void setValue(AbstractValue value) { + this.value = value; + } + } + + enum EnumValue { + FIRST + } + + class WithEnumValue { + private EnumValue value; + + public EnumValue getValue() { + return value; + } + + public void setValue(EnumValue value) { + this.value = value; + } + } + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateLocalDate(@MappingTarget WithLocalDate target, WithLocalDate source); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateBigDecimal(@MappingTarget WithBigDecimal target, WithBigDecimal source); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateComparable(@MappingTarget WithComparable target, WithComparable source); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateAbstractValue(@MappingTarget WithAbstractValue target, WithAbstractValue source); + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void updateEnumValue(@MappingTarget WithEnumValue target, WithEnumValue source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/Issue4060Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/Issue4060Test.java new file mode 100644 index 0000000000..e0e51dac32 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/Issue4060Test.java @@ -0,0 +1,77 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._4060; + +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult; +import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; +import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("4060") +public class Issue4060Test { + + @ProcessorTest + @WithClasses(ErroneousSetToDefaultMapper.class) + @ExpectedCompilationOutcome(value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 87, + message = "LocalDate does not have an accessible parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression."), + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 90, + message = "BigDecimal does not have an accessible parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression."), + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 93, + message = "Comparable does not have an accessible parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression."), + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 96, + message = "ErroneousSetToDefaultMapper.AbstractValue does not have an accessible " + + "parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression."), + @Diagnostic(type = ErroneousSetToDefaultMapper.class, + kind = javax.tools.Diagnostic.Kind.ERROR, + line = 99, + message = "ErroneousSetToDefaultMapper.EnumValue does not have an accessible " + + "parameterless constructor. " + + "Either change the nullValuePropertyMappingStrategy or define a defaultValue " + + "or a defaultExpression.") + }) + void setToDefaultWithoutParameterlessConstructorFails() { + } + + @ProcessorTest + @WithClasses(SetToDefaultMapper.class) + void setToDefaultWithParameterlessConstructorResetsToNewInstance() { + SetToDefaultMapper.Target target = new SetToDefaultMapper.Target(); + SetToDefaultMapper.Nested existing = new SetToDefaultMapper.Nested(); + existing.setName( "existing" ); + target.setNested( existing ); + target.setText( "existing" ); + + SetToDefaultMapper.INSTANCE.update( target, new SetToDefaultMapper.Source() ); + + // source properties are null, SET_TO_DEFAULT resets them to a fresh default + assertThat( target.getNested() ).isNotNull(); + assertThat( target.getNested() ).isNotSameAs( existing ); + assertThat( target.getNested().getName() ).isNull(); + assertThat( target.getText() ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/SetToDefaultMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/SetToDefaultMapper.java new file mode 100644 index 0000000000..edfe7e9b3b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_4060/SetToDefaultMapper.java @@ -0,0 +1,75 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.bugs._4060; + +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.NullValuePropertyMappingStrategy; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SetToDefaultMapper { + + SetToDefaultMapper INSTANCE = Mappers.getMapper( SetToDefaultMapper.class ); + + class Nested { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + class Target { + private Nested nested; + private String text; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + + class Source { + private Nested nested; + private String text; + + public Nested getNested() { + return nested; + } + + public void setNested(Nested nested) { + this.nested = nested; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + } + + @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT) + void update(@MappingTarget Target target, Source source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java new file mode 100644 index 0000000000..8bac5841f1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ClassAccessibilityTest.java @@ -0,0 +1,49 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.classaccessibility; + +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static java.lang.reflect.Modifier.isPrivate; +import static java.lang.reflect.Modifier.isProtected; +import static java.lang.reflect.Modifier.isPublic; +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ + Source.class, + Target.class, + PublicAbstractionMapper.class, + PackageAbstractionMapper.class, + ForcedPublicMapper.class, + ForcedPackagePrivateMapper.class +}) +public class ClassAccessibilityTest { + + @ProcessorTest + public void shouldCreateModifierAccordingToAnnotation() throws Exception { + Class publicLike = loadForMapper( PublicAbstractionMapper.class ); + assertThat( isPublic( publicLike.getModifiers() ) ).isTrue(); + + Class packageLike = loadForMapper( PackageAbstractionMapper.class ); + assertThat( isDefault( packageLike.getModifiers() ) ).isTrue(); + + Class forcedPublic = loadForMapper( ForcedPublicMapper.class ); + assertThat( isPublic( forcedPublic.getModifiers() ) ).isTrue(); + + Class forcedDefault = loadForMapper( ForcedPackagePrivateMapper.class ); + assertThat( isDefault( forcedDefault.getModifiers() ) ).isTrue(); + } + + private static Class loadForMapper(Class mapper) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass( mapper.getName() + "Impl" ); + } + + private static boolean isDefault(int modifiers) { + return !isPublic( modifiers ) && !isProtected( modifiers ) && !isPrivate( modifiers ); + } +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.java new file mode 100644 index 0000000000..191aadb2a0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPackagePrivateMapper.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.classaccessibility; + +import org.mapstruct.ClassAccessibility; +import org.mapstruct.Mapper; + +@Mapper(accessibility = ClassAccessibility.PACKAGE_PRIVATE) +public interface ForcedPackagePrivateMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java new file mode 100644 index 0000000000..fd6ced910a --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/ForcedPublicMapper.java @@ -0,0 +1,15 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.classaccessibility; + +import org.mapstruct.ClassAccessibility; +import org.mapstruct.Mapper; + +@Mapper(accessibility = ClassAccessibility.PUBLIC) +interface ForcedPublicMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java new file mode 100644 index 0000000000..d9f426b382 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PackageAbstractionMapper.java @@ -0,0 +1,14 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.classaccessibility; + +import org.mapstruct.Mapper; + +@Mapper +interface PackageAbstractionMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java new file mode 100644 index 0000000000..7c1b5de10f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/PublicAbstractionMapper.java @@ -0,0 +1,14 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.classaccessibility; + +import org.mapstruct.Mapper; + +@Mapper +public interface PublicAbstractionMapper { + Target map(Source value); +} + diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java new file mode 100644 index 0000000000..feff965719 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Source.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.classaccessibility; + +/** + * @author Raimund Klein + **/ +public class Source { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java new file mode 100644 index 0000000000..e8c50a2531 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/classaccessibility/Target.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.classaccessibility; + +/** + * @author Raimund Klein + **/ +public class Target { + private String foo; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/ZonedOffsetDateTimeToLocalDateTimeConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/ZonedOffsetDateTimeToLocalDateTimeConversionTest.java new file mode 100644 index 0000000000..ab17980e96 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/ZonedOffsetDateTimeToLocalDateTimeConversionTest.java @@ -0,0 +1,113 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion.Source; +import org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion.SourceTargetMapper; +import org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion.Target; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@WithClasses({ Source.class, Target.class, SourceTargetMapper.class }) +@IssueKey("4027") +public class ZonedOffsetDateTimeToLocalDateTimeConversionTest { + + @RegisterExtension + GeneratedSource generatedSource = new GeneratedSource() + .addComparisonToFixtureFor( SourceTargetMapper.class ); + + @ProcessorTest + public void testZonedDateTimeToLocalDateTimeMapping() { + ZonedDateTime zonedDateTime = ZonedDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneId.of( "UTC" ) ); + Source source = new Source(); + source.setZonedDateTime( zonedDateTime ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + assertThat( target.getZonedDateTime() ) + .isEqualTo( LocalDateTime.of( 2024, 1, 1, 12, 30, 0 ) ); + } + + @ProcessorTest + public void testOffsetDateTimeToLocalDateTimeMapping() { + OffsetDateTime offsetDateTime = OffsetDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ); + Source source = new Source(); + source.setOffsetDateTime( offsetDateTime ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + assertThat( target.getOffsetDateTime() ) + .isEqualTo( LocalDateTime.of( 2024, 1, 1, 12, 30, 0 ) ); + } + + @ProcessorTest + public void testLocalDateTimeToZonedDateTimeMapping() { + LocalDateTime localDateTime = LocalDateTime.of( 2024, 1, 1, 12, 30, 0 ); + Target target = new Target(); + target.setZonedDateTime( localDateTime ); + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + assertThat( source.getZonedDateTime() ) + .isEqualTo( ZonedDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ) ); + } + + @ProcessorTest + public void testLocalDateTimeToOffsetDateTimeMapping() { + LocalDateTime localDateTime = LocalDateTime.of( 2024, 1, 1, 12, 30, 0 ); + Target target = new Target(); + target.setOffsetDateTime( localDateTime ); + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + assertThat( source.getOffsetDateTime() ) + .isEqualTo( OffsetDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ) ); + } + + @ProcessorTest + public void testZonedDateTimeToInstantMapping() { + ZonedDateTime zonedDateTime = ZonedDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ); + Source source = new Source(); + source.setZonedDateTimeAsInstant( zonedDateTime ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + assertThat( target.getZonedDateTimeAsInstant() ) + .isEqualTo( Instant.parse( "2024-01-01T12:30:00Z" ) ); + } + + @ProcessorTest + public void testOffsetDateTimeToInstantMapping() { + OffsetDateTime offsetDateTime = OffsetDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ); + Source source = new Source(); + source.setOffsetDateTimeAsInstant( offsetDateTime ); + Target target = SourceTargetMapper.INSTANCE.toTarget( source ); + assertThat( target.getOffsetDateTimeAsInstant() ) + .isEqualTo( Instant.parse( "2024-01-01T12:30:00Z" ) ); + } + + @ProcessorTest + public void testInstantToZonedDateTimeMapping() { + Instant instant = Instant.parse( "2024-01-01T12:30:00Z" ); + Target target = new Target(); + target.setZonedDateTimeAsInstant( instant ); + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + assertThat( source.getZonedDateTimeAsInstant() ) + .isEqualTo( ZonedDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ) ); + } + + @ProcessorTest + public void testInstantToOffsetDateTimeMapping() { + Instant instant = Instant.parse( "2024-01-01T12:30:00Z" ); + Target target = new Target(); + target.setOffsetDateTimeAsInstant( instant ); + Source source = SourceTargetMapper.INSTANCE.toSource( target ); + assertThat( source.getOffsetDateTimeAsInstant() ) + .isEqualTo( OffsetDateTime.of( 2024, 1, 1, 12, 30, 0, 0, ZoneOffset.UTC ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Source.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Source.java new file mode 100644 index 0000000000..18bd99825c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Source.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion; + +import java.time.OffsetDateTime; +import java.time.ZonedDateTime; + +public class Source { + private ZonedDateTime zonedDateTime; + private OffsetDateTime offsetDateTime; + private ZonedDateTime zonedDateTimeAsInstant; + private OffsetDateTime offsetDateTimeAsInstant; + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public OffsetDateTime getOffsetDateTime() { + return offsetDateTime; + } + + public void setOffsetDateTime(OffsetDateTime offsetDateTime) { + this.offsetDateTime = offsetDateTime; + } + + public ZonedDateTime getZonedDateTimeAsInstant() { + return zonedDateTimeAsInstant; + } + + public void setZonedDateTimeAsInstant(ZonedDateTime zonedDateTimeAsInstant) { + this.zonedDateTimeAsInstant = zonedDateTimeAsInstant; + } + + public OffsetDateTime getOffsetDateTimeAsInstant() { + return offsetDateTimeAsInstant; + } + + public void setOffsetDateTimeAsInstant(OffsetDateTime offsetDateTimeAsInstant) { + this.offsetDateTimeAsInstant = offsetDateTimeAsInstant; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapper.java new file mode 100644 index 0000000000..43bee5f77f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapper.java @@ -0,0 +1,18 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceTargetMapper { + SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); + + Target toTarget(Source source); + + Source toSource(Target target); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Target.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Target.java new file mode 100644 index 0000000000..b43b9f6843 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/Target.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion; + +import java.time.Instant; +import java.time.LocalDateTime; + +public class Target { + private LocalDateTime zonedDateTime; + private LocalDateTime offsetDateTime; + private Instant zonedDateTimeAsInstant; + private Instant offsetDateTimeAsInstant; + + public LocalDateTime getOffsetDateTime() { + return offsetDateTime; + } + + public void setOffsetDateTime(LocalDateTime offsetDateTime) { + this.offsetDateTime = offsetDateTime; + } + + public LocalDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(LocalDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public Instant getZonedDateTimeAsInstant() { + return zonedDateTimeAsInstant; + } + + public void setZonedDateTimeAsInstant(Instant zonedDateTimeAsInstant) { + this.zonedDateTimeAsInstant = zonedDateTimeAsInstant; + } + + public Instant getOffsetDateTimeAsInstant() { + return offsetDateTimeAsInstant; + } + + public void setOffsetDateTimeAsInstant(Instant offsetDateTimeAsInstant) { + this.offsetDateTimeAsInstant = offsetDateTimeAsInstant; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/conversion/url/URLConversionTest.java index 1184134cf3..f5a3b7f7e8 100644 --- 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 @@ -6,7 +6,7 @@ package org.mapstruct.ap.test.conversion.url; import java.net.MalformedURLException; -import java.net.URL; +import java.net.URI; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -25,7 +25,7 @@ public class URLConversionTest { @ProcessorTest public void shouldApplyURLConversion() throws MalformedURLException { Source source = new Source(); - source.setURL( new URL("https://mapstruct.org/") ); + source.setURL( URI.create( "https://mapstruct.org/" ).toURL() ); Target target = URLMapper.INSTANCE.sourceToTarget( source ); @@ -41,13 +41,13 @@ public void shouldApplyReverseURLConversion() throws MalformedURLException { Source source = URLMapper.INSTANCE.targetToSource( target ); assertThat( source ).isNotNull(); - assertThat( source.getURL() ).isEqualTo( new URL( target.getURL() ) ); + assertThat( source.getURL() ).isEqualTo( URI.create( target.getURL() ).toURL() ); } @ProcessorTest public void shouldHandleInvalidURLString() { Target target = new Target(); - target.setInvalidURL( "XXXXXXXXX" ); + target.setInvalidURL( "xxxxxx://mapstruct.org/" ); assertThatThrownBy( () -> URLMapper.INSTANCE.targetToSource( target ) ) .isInstanceOf( RuntimeException.class ) @@ -57,7 +57,7 @@ public void shouldHandleInvalidURLString() { @ProcessorTest public void shouldHandleInvalidURLStringWithMalformedURLException() { Target target = new Target(); - target.setInvalidURL( "XXXXXXXXX" ); + target.setInvalidURL( "xxxxxx://mapstruct.org/" ); assertThatThrownBy( () -> URLMapper.INSTANCE.targetToSourceWithMalformedURLException( target ) ) .isInstanceOf( MalformedURLException.class ); 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 index f460180651..57c2099b8b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java @@ -10,6 +10,7 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.mapstruct.ClassAccessibility; import org.mapstruct.CollectionMappingStrategy; import org.mapstruct.ConditionStrategy; import org.mapstruct.InjectionStrategy; @@ -17,6 +18,7 @@ import org.mapstruct.NullValueCheckStrategy; import org.mapstruct.NullValueMappingStrategy; import org.mapstruct.ReportingPolicy; +import org.mapstruct.ap.internal.gem.ClassAccessibilityGem; import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem; import org.mapstruct.ap.internal.gem.ConditionStrategyGem; import org.mapstruct.ap.internal.gem.InjectionStrategyGem; @@ -75,6 +77,12 @@ public void conditionStrategyGemIsCorrect() { namesOf( ConditionStrategyGem.values() ) ); } + @Test + public void classAccesibilityGemIsCorrect() { + assertThat( namesOf( ClassAccessibility.values() ) ).isEqualTo( + namesOf( ClassAccessibilityGem.values() ) ); + } + private static List namesOf(Enum[] values) { return Stream.of( values ) .map( Enum::name ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java index 81dc9a452a..3caeb8cbba 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mapperconfig/CentralConfig.java @@ -5,6 +5,7 @@ */ package org.mapstruct.ap.test.mapperconfig; +import org.mapstruct.ClassAccessibility; import org.mapstruct.MapperConfig; import org.mapstruct.ReportingPolicy; @@ -12,7 +13,9 @@ * * @author Sjaak Derksen */ -@MapperConfig(uses = { CustomMapperViaMapperConfig.class }, unmappedTargetPolicy = ReportingPolicy.ERROR ) +@MapperConfig(uses = {CustomMapperViaMapperConfig.class}, + unmappedTargetPolicy = ReportingPolicy.ERROR, + accessibility = ClassAccessibility.PACKAGE_PRIVATE) public class CentralConfig { } 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 1f7fe423f5..a13db4ab0b 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 @@ -12,6 +12,9 @@ import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; +import static java.lang.reflect.Modifier.isPrivate; +import static java.lang.reflect.Modifier.isProtected; +import static java.lang.reflect.Modifier.isPublic; import static org.assertj.core.api.Assertions.assertThat; /** @@ -49,6 +52,13 @@ public void shouldUseCustomMapperViaMapperConfigForFooToDto() { assertThat( source.getFoo().getCreatedBy() ).isEqualTo( CustomMapperViaMapperConfig.class.getSimpleName() ); } + @ProcessorTest + @WithClasses( { Target.class, SourceTargetMapper.class } ) + public void shouldDeclareMapperImplementationAsPackagePrivate() throws ClassNotFoundException { + Class implementation = loadForMapper( SourceTargetMapper.class ); + assertThat( isDefault( implementation.getModifiers() ) ).isTrue(); + } + @ProcessorTest @WithClasses( { TargetNoFoo.class, SourceTargetMapperWarn.class } ) @ExpectedCompilationOutcome(value = CompilationResult.SUCCEEDED, @@ -70,4 +80,12 @@ public void shouldUseWARNViaMapper() { }) public void shouldUseERRORViaMapperConfig() { } + + private static Class loadForMapper(Class mapper) throws ClassNotFoundException { + return Thread.currentThread().getContextClassLoader().loadClass( mapper.getName() + "Impl" ); + } + + private static boolean isDefault(int modifiers) { + return !isPublic( modifiers ) && !isProtected( modifiers ) && !isPrivate( modifiers ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyMapper.java new file mode 100644 index 0000000000..d705652832 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyMapper.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.nullcheck.jspecify; + +import org.jspecify.annotations.NullMarked; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyCollectionPropertyMapper { + + JSpecifyCollectionPropertyMapper INSTANCE = Mappers.getMapper( JSpecifyCollectionPropertyMapper.class ); + + NullMarkedCollectionTargetBean map(NullMarkedCollectionSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyTest.java new file mode 100644 index 0000000000..bf194fc08f --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyTest.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.nullcheck.jspecify; + +import java.util.Arrays; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1243") +@WithJSpecify +class JSpecifyCollectionPropertyTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedCollectionSourceBean.class, + NullMarkedCollectionTargetBean.class, + JSpecifyCollectionPropertyMapper.class + }) + void collectionPropertyWithNonNullSourceSkipsNullCheck() { + generatedSource.addComparisonToFixtureFor( JSpecifyCollectionPropertyMapper.class ); + + NullMarkedCollectionSourceBean source = new NullMarkedCollectionSourceBean(); + source.setValues( Arrays.asList( "a", "b", "c" ) ); + + NullMarkedCollectionTargetBean target = JSpecifyCollectionPropertyMapper.INSTANCE.map( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getValues() ).containsExactly( "a", "b", "c" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledContainerSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledContainerSourceMapper.java new file mode 100644 index 0000000000..2de32c4235 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledContainerSourceMapper.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * Dedicated to {@link JSpecifyDisabledTest} so its generated implementation is not shared (and pre-loaded) by an + * enabled test in the same JVM. With JSpecify enabled the {@code @NonNull} source would skip the method-level guard. + */ +@NullMarked +@Mapper +public interface JSpecifyDisabledContainerSourceMapper { + + JSpecifyDisabledContainerSourceMapper INSTANCE = + Mappers.getMapper( JSpecifyDisabledContainerSourceMapper.class ); + + List mapAll(List sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledNonNullReturnMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledNonNullReturnMapper.java new file mode 100644 index 0000000000..bcdb0bc0ca --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledNonNullReturnMapper.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * Dedicated to {@link JSpecifyDisabledTest} so its generated implementation is not shared (and pre-loaded) by an + * enabled test in the same JVM. With JSpecify enabled the {@code @NonNull} return would force RETURN_DEFAULT. + */ +@NullMarked +@Mapper +public interface JSpecifyDisabledNonNullReturnMapper { + + JSpecifyDisabledNonNullReturnMapper INSTANCE = + Mappers.getMapper( JSpecifyDisabledNonNullReturnMapper.class ); + + List mapAll(@Nullable List sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledTest.java index 547365467f..d669952737 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyDisabledTest.java @@ -54,4 +54,30 @@ public void disabledFlagSkipsConstructorNonNullHardError() { // The JSpecify-driven hard error for a @Nullable source mapped to a @NonNull constructor // parameter is purely a JSpecify signal; with the flag disabled it must not be raised. } + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyDisabledContainerSourceMapper.class + }) + public void disabledFlagKeepsContainerMethodSourceGuard() { + // With JSpecify enabled the @NonNull source parameter skips the method-level null guard. When disabled, + // the resolver reports UNKNOWN, so the unconditional "if (sources == null) return null;" guard is kept and + // mapping a null source returns null rather than throwing an NPE. + assertThat( JSpecifyDisabledContainerSourceMapper.INSTANCE.mapAll( null ) ).isNull(); + } + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyDisabledNonNullReturnMapper.class + }) + public void disabledFlagSkipsNonNullReturnForcing() { + // With JSpecify enabled the @NonNull return forces RETURN_DEFAULT (empty list for a null source). When + // disabled, that forcing is suppressed and the default RETURN_NULL strategy applies, so a null source + // maps to null. + assertThat( JSpecifyDisabledNonNullReturnMapper.INSTANCE.mapAll( null ) ).isNull(); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodMapper.java new file mode 100644 index 0000000000..5a1d0b0d68 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodMapper.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyIterableMethodMapper { + + JSpecifyIterableMethodMapper INSTANCE = Mappers.getMapper( JSpecifyIterableMethodMapper.class ); + + List mapAll(List sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodTest.java new file mode 100644 index 0000000000..ceed2236f6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodTest.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.nullcheck.jspecify; + +import java.util.Arrays; +import java.util.List; + +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1243") +@WithJSpecify +class JSpecifyIterableMethodTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyIterableMethodMapper.class + }) + void iterableMethodWithNonNullSourceSkipsMethodGuard() { + generatedSource.addComparisonToFixtureFor( JSpecifyIterableMethodMapper.class ); + + NullMarkedSourceBean source = new NullMarkedSourceBean(); + source.setNonNullByDefault( "value" ); + + List targets = JSpecifyIterableMethodMapper.INSTANCE.mapAll( Arrays.asList( source ) ); + + assertThat( targets ).hasSize( 1 ); + assertThat( targets.get( 0 ).getNonNullByDefault() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapper.java new file mode 100644 index 0000000000..cdb844795b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapper.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.nullcheck.jspecify; + +import java.util.Map; + +import org.jspecify.annotations.NullMarked; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyMapMethodMapper { + + JSpecifyMapMethodMapper INSTANCE = Mappers.getMapper( JSpecifyMapMethodMapper.class ); + + Map mapAll(Map sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodTest.java new file mode 100644 index 0000000000..4673841873 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodTest.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.nullcheck.jspecify; + +import java.util.Collections; +import java.util.Map; + +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1243") +@WithJSpecify +class JSpecifyMapMethodTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyMapMethodMapper.class + }) + void mapMethodWithNonNullSourceSkipsMethodGuard() { + generatedSource.addComparisonToFixtureFor( JSpecifyMapMethodMapper.class ); + + NullMarkedSourceBean source = new NullMarkedSourceBean(); + source.setNonNullByDefault( "value" ); + + Map targets = + JSpecifyMapMethodMapper.INSTANCE.mapAll( Collections.singletonMap( "k", source ) ); + + assertThat( targets ).hasSize( 1 ); + assertThat( targets.get( "k" ).getNonNullByDefault() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanMapper.java new file mode 100644 index 0000000000..8401e56682 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanMapper.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.nullcheck.jspecify; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyNonNullReturnBeanMapper { + + JSpecifyNonNullReturnBeanMapper INSTANCE = Mappers.getMapper( JSpecifyNonNullReturnBeanMapper.class ); + + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "nonNullByDefault", source = "value") + NullMarkedTargetBean map(@Nullable JSpecifyNonNullReturnBeanSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanSourceBean.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanSourceBean.java new file mode 100644 index 0000000000..d4bdb926c5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanSourceBean.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.nullcheck.jspecify; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class JSpecifyNonNullReturnBeanSourceBean { + + 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/nullcheck/jspecify/JSpecifyNonNullReturnBeanTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanTest.java new file mode 100644 index 0000000000..ccac0e0653 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanTest.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.nullcheck.jspecify; + +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1243") +@WithJSpecify +class JSpecifyNonNullReturnBeanTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + JSpecifyNonNullReturnBeanSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyNonNullReturnBeanMapper.class + }) + void nonNullReturnForcesMapNullToDefault() { + generatedSource.addComparisonToFixtureFor( JSpecifyNonNullReturnBeanMapper.class ); + + NullMarkedTargetBean fromNull = JSpecifyNonNullReturnBeanMapper.INSTANCE.map( null ); + + assertThat( fromNull ).isNotNull(); + assertThat( fromNull.getNonNullByDefault() ).isNull(); + + JSpecifyNonNullReturnBeanSourceBean source = new JSpecifyNonNullReturnBeanSourceBean(); + source.setValue( "value" ); + + NullMarkedTargetBean fromSource = JSpecifyNonNullReturnBeanMapper.INSTANCE.map( source ); + + assertThat( fromSource ).isNotNull(); + assertThat( fromSource.getNonNullByDefault() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableMapper.java new file mode 100644 index 0000000000..4f3fd1c0e2 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableMapper.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyNonNullReturnIterableMapper { + + JSpecifyNonNullReturnIterableMapper INSTANCE = Mappers.getMapper( JSpecifyNonNullReturnIterableMapper.class ); + + List mapAll(@Nullable List sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableTest.java new file mode 100644 index 0000000000..f4ba29bb92 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableTest.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.nullcheck.jspecify; + +import java.util.Arrays; +import java.util.List; + +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1243") +@WithJSpecify +class JSpecifyNonNullReturnIterableTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyNonNullReturnIterableMapper.class + }) + void nonNullReturnIterableForcesEmptyDefault() { + generatedSource.addComparisonToFixtureFor( JSpecifyNonNullReturnIterableMapper.class ); + + List fromNull = JSpecifyNonNullReturnIterableMapper.INSTANCE.mapAll( null ); + + assertThat( fromNull ).isEmpty(); + + NullMarkedSourceBean source = new NullMarkedSourceBean(); + source.setNonNullByDefault( "value" ); + + List fromSource = + JSpecifyNonNullReturnIterableMapper.INSTANCE.mapAll( Arrays.asList( source ) ); + + assertThat( fromSource ).hasSize( 1 ); + assertThat( fromSource.get( 0 ).getNonNullByDefault() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapper.java new file mode 100644 index 0000000000..8e6a0cb885 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapper.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.nullcheck.jspecify; + +import java.util.Map; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyNonNullReturnMapMapper { + + JSpecifyNonNullReturnMapMapper INSTANCE = Mappers.getMapper( JSpecifyNonNullReturnMapMapper.class ); + + Map mapAll(@Nullable Map sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapTest.java new file mode 100644 index 0000000000..b96d3f3fc0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapTest.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.nullcheck.jspecify; + +import java.util.Collections; +import java.util.Map; + +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1243") +@WithJSpecify +class JSpecifyNonNullReturnMapTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyNonNullReturnMapMapper.class + }) + void nonNullReturnMapForcesEmptyDefault() { + generatedSource.addComparisonToFixtureFor( JSpecifyNonNullReturnMapMapper.class ); + + Map fromNull = JSpecifyNonNullReturnMapMapper.INSTANCE.mapAll( null ); + + assertThat( fromNull ).isEmpty(); + + NullMarkedSourceBean source = new NullMarkedSourceBean(); + source.setNonNullByDefault( "value" ); + + Map fromSource = + JSpecifyNonNullReturnMapMapper.INSTANCE.mapAll( Collections.singletonMap( "k", source ) ); + + assertThat( fromSource ).hasSize( 1 ); + assertThat( fromSource.get( "k" ).getNonNullByDefault() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamMapper.java new file mode 100644 index 0000000000..62d061a001 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamMapper.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.nullcheck.jspecify; + +import java.util.stream.Stream; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyNonNullReturnStreamMapper { + + JSpecifyNonNullReturnStreamMapper INSTANCE = Mappers.getMapper( JSpecifyNonNullReturnStreamMapper.class ); + + Stream mapAll(@Nullable Stream sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamTest.java new file mode 100644 index 0000000000..fa62973ebf --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamTest.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.nullcheck.jspecify; + +import java.util.List; +import java.util.stream.Collectors; +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1243") +@WithJSpecify +class JSpecifyNonNullReturnStreamTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyNonNullReturnStreamMapper.class + }) + void nonNullReturnStreamForcesEmptyDefault() { + generatedSource.addComparisonToFixtureFor( JSpecifyNonNullReturnStreamMapper.class ); + + Stream fromNull = JSpecifyNonNullReturnStreamMapper.INSTANCE.mapAll( null ); + + assertThat( fromNull ).isNotNull(); + assertThat( fromNull.collect( Collectors.toList() ) ).isEmpty(); + + NullMarkedSourceBean source = new NullMarkedSourceBean(); + source.setNonNullByDefault( "value" ); + + List fromSource = JSpecifyNonNullReturnStreamMapper.INSTANCE + .mapAll( Stream.of( source ) ) + .collect( Collectors.toList() ); + + assertThat( fromSource ).hasSize( 1 ); + assertThat( fromSource.get( 0 ).getNonNullByDefault() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableMapper.java new file mode 100644 index 0000000000..b79763d712 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableMapper.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.Mapper; +import org.mapstruct.MappingTarget; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyNonNullReturnUpdateIterableMapper { + + JSpecifyNonNullReturnUpdateIterableMapper INSTANCE = + Mappers.getMapper( JSpecifyNonNullReturnUpdateIterableMapper.class ); + + List mapAll(@Nullable List sources, + @MappingTarget List target); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableTest.java new file mode 100644 index 0000000000..56e71e6995 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableTest.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.nullcheck.jspecify; + +import java.util.ArrayList; +import java.util.List; + +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * The @NonNull-return forcing is gated on {@code !isUpdateMethod()}. An update (existing-instance) method must not + * be forced to RETURN_DEFAULT: a null source returns the supplied target instance, not a fresh empty collection. + */ +@IssueKey("1243") +@WithJSpecify +class JSpecifyNonNullReturnUpdateIterableTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyNonNullReturnUpdateIterableMapper.class + }) + void updateMethodWithNonNullReturnIsNotForced() { + generatedSource.addComparisonToFixtureFor( JSpecifyNonNullReturnUpdateIterableMapper.class ); + + List target = new ArrayList<>(); + + List result = JSpecifyNonNullReturnUpdateIterableMapper.INSTANCE.mapAll( null, target ); + + assertThat( result ).isSameAs( target ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableMapper.java new file mode 100644 index 0000000000..026890dee8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableMapper.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyNullableSourceIterableMapper { + + JSpecifyNullableSourceIterableMapper INSTANCE = Mappers.getMapper( JSpecifyNullableSourceIterableMapper.class ); + + @Nullable + List mapAll(@Nullable List sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableTest.java new file mode 100644 index 0000000000..da77fe71e1 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableTest.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.nullcheck.jspecify; + +import java.util.Arrays; +import java.util.List; + +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Negative counterpart to {@link JSpecifyIterableMethodTest}: a {@code @Nullable} source parameter (and a + * {@code @Nullable} return, so the @NonNull-return forcing does not kick in) must keep the method-level + * {@code if ( sources == null ) return null;} guard. + */ +@IssueKey("1243") +@WithJSpecify +class JSpecifyNullableSourceIterableTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyNullableSourceIterableMapper.class + }) + void nullableSourceKeepsMethodGuard() { + generatedSource.addComparisonToFixtureFor( JSpecifyNullableSourceIterableMapper.class ); + + assertThat( JSpecifyNullableSourceIterableMapper.INSTANCE.mapAll( null ) ).isNull(); + + NullMarkedSourceBean source = new NullMarkedSourceBean(); + source.setNonNullByDefault( "value" ); + + List targets = + JSpecifyNullableSourceIterableMapper.INSTANCE.mapAll( Arrays.asList( source ) ); + + assertThat( targets ).hasSize( 1 ); + assertThat( targets.get( 0 ).getNonNullByDefault() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableMapper.java new file mode 100644 index 0000000000..387103bae5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableMapper.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import org.mapstruct.IterableMapping; +import org.mapstruct.Mapper; +import org.mapstruct.NullValueMappingStrategy; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyReturnNullOverrideIterableMapper { + + JSpecifyReturnNullOverrideIterableMapper INSTANCE = + Mappers.getMapper( JSpecifyReturnNullOverrideIterableMapper.class ); + + // Explicit RETURN_NULL, but the @NonNull return type (NullMarked scope) wins and forces RETURN_DEFAULT. + @IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL) + List mapAll(@Nullable List sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableTest.java new file mode 100644 index 0000000000..eaea578903 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableTest.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.nullcheck.jspecify; + +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies that a JSpecify {@code @NonNull} return type wins over an explicitly configured + * {@code NullValueMappingStrategy.RETURN_NULL}: the generated method returns an empty collection rather + * than {@code null}. + */ +@IssueKey("1243") +@WithJSpecify +class JSpecifyReturnNullOverrideIterableTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyReturnNullOverrideIterableMapper.class + }) + void nonNullReturnOverridesExplicitReturnNull() { + generatedSource.addComparisonToFixtureFor( JSpecifyReturnNullOverrideIterableMapper.class ); + + assertThat( JSpecifyReturnNullOverrideIterableMapper.INSTANCE.mapAll( null ) ).isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodMapper.java new file mode 100644 index 0000000000..1b069af5b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodMapper.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.nullcheck.jspecify; + +import java.util.stream.Stream; + +import org.jspecify.annotations.NullMarked; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@NullMarked +@Mapper +public interface JSpecifyStreamMethodMapper { + + JSpecifyStreamMethodMapper INSTANCE = Mappers.getMapper( JSpecifyStreamMethodMapper.class ); + + Stream mapAll(Stream sources); + + NullMarkedTargetBean map(NullMarkedSourceBean source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodTest.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodTest.java new file mode 100644 index 0000000000..76c7af8792 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodTest.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.nullcheck.jspecify; + +import java.util.List; +import java.util.stream.Collectors; +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.WithJSpecify; +import org.mapstruct.ap.testutil.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +@IssueKey("1243") +@WithJSpecify +class JSpecifyStreamMethodTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + NullMarkedSourceBean.class, + NullMarkedTargetBean.class, + JSpecifyStreamMethodMapper.class + }) + void streamMethodWithNonNullSourceSkipsMethodGuard() { + generatedSource.addComparisonToFixtureFor( JSpecifyStreamMethodMapper.class ); + + NullMarkedSourceBean source = new NullMarkedSourceBean(); + source.setNonNullByDefault( "value" ); + + List targets = JSpecifyStreamMethodMapper.INSTANCE + .mapAll( Stream.of( source ) ) + .collect( Collectors.toList() ); + + assertThat( targets ).hasSize( 1 ); + assertThat( targets.get( 0 ).getNonNullByDefault() ).isEqualTo( "value" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/NullMarkedCollectionSourceBean.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/NullMarkedCollectionSourceBean.java new file mode 100644 index 0000000000..3c67457637 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/NullMarkedCollectionSourceBean.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; + +/** + * Source bean with a {@code @NonNull} collection getter (via @NullMarked scope). + */ +@NullMarked +public class NullMarkedCollectionSourceBean { + + private List values; + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/NullMarkedCollectionTargetBean.java b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/NullMarkedCollectionTargetBean.java new file mode 100644 index 0000000000..eae3adc1a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/nullcheck/jspecify/NullMarkedCollectionTargetBean.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.nullcheck.jspecify; + +import java.util.List; + +import org.jspecify.annotations.NullMarked; + +@NullMarked +public class NullMarkedCollectionTargetBean { + + private List values; + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/CrossPackageOptionalBuilderMapper.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/CrossPackageOptionalBuilderMapper.java new file mode 100644 index 0000000000..bdff608ddc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/CrossPackageOptionalBuilderMapper.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.optional.builder.crosspackage; + +import java.util.Optional; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.optional.builder.crosspackage.dto.PersonTarget; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface CrossPackageOptionalBuilderMapper { + + CrossPackageOptionalBuilderMapper INSTANCE = Mappers.getMapper( CrossPackageOptionalBuilderMapper.class ); + + Optional toTarget(PersonSource source); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/CrossPackageOptionalBuilderTest.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/CrossPackageOptionalBuilderTest.java new file mode 100644 index 0000000000..8919787dfb --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/CrossPackageOptionalBuilderTest.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.optional.builder.crosspackage; + +import java.util.Optional; + +import org.mapstruct.ap.test.optional.builder.crosspackage.dto.PersonTarget; +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( "4046" ) +@WithClasses({ CrossPackageOptionalBuilderMapper.class, PersonSource.class, PersonTarget.class }) +class CrossPackageOptionalBuilderTest { + + @ProcessorTest + void shouldUseStaticBuilderMethodForOptionalTargetAcrossPackages() { + PersonSource source = new PersonSource( "John" ); + Optional result = CrossPackageOptionalBuilderMapper.INSTANCE.toTarget( source ); + + assertThat( result ).isPresent(); + assertThat( result.get().getName() ).isEqualTo( "John" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/PersonSource.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/PersonSource.java new file mode 100644 index 0000000000..dea0c2f2b8 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/PersonSource.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.builder.crosspackage; + +public class PersonSource { + private final String name; + + public PersonSource(String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/dto/PersonTarget.java b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/dto/PersonTarget.java new file mode 100644 index 0000000000..6fb5ab770b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/optional/builder/crosspackage/dto/PersonTarget.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.builder.crosspackage.dto; + +public class PersonTarget { + + private final String name; + + private PersonTarget(PersonTargetBuilder builder) { + this.name = builder.name; + } + + public String getName() { + return name; + } + + public static PersonTargetBuilder builder() { + return new PersonTargetBuilder(); + } + + public static class PersonTargetBuilder { + + private String name; + + PersonTargetBuilder() { } + + public PersonTargetBuilder name(String name) { + this.name = name; + return this; + } + + public PersonTarget build() { + return new PersonTarget( this ); + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/source/nullvaluecheckstrategy/Stage.java b/processor/src/test/java/org/mapstruct/ap/test/source/nullvaluecheckstrategy/Stage.java index 3a13f0aa1c..be70aa9754 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/source/nullvaluecheckstrategy/Stage.java +++ b/processor/src/test/java/org/mapstruct/ap/test/source/nullvaluecheckstrategy/Stage.java @@ -14,9 +14,9 @@ */ public enum Stage { - MAIN("Paul McCartney", "Ellie Goulding", "Disclosure", "Kaiser Chiefs", "Rammstein"), - KLUB_C("James Blake", "Lost Frequencies"), - THE_BARN("New Order", "Year and Years"); + MAIN( "Paul McCartney", "Ellie Goulding", "Disclosure", "Kaiser Chiefs", "Rammstein" ), + KLUB_C( "James Blake", "Lost Frequencies" ), + THE_BARN( "New Order", "Year and Years" ); private final List artists; 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 2f8b13ba25..277b552f72 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 @@ -10,6 +10,7 @@ import java.io.UncheckedIOException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -18,6 +19,7 @@ import org.assertj.core.internal.Diff; import org.assertj.core.internal.Failures; import org.assertj.core.util.diff.Delta; +import org.opentest4j.FileInfo; import static java.lang.String.format; @@ -81,8 +83,22 @@ public void hasSameMapperContent(File expected) { ) ); diffs.removeIf( this::ignoreDelta ); if ( !diffs.isEmpty() ) { + FileInfo actualInfo; + FileInfo expectedInfo; + try { + actualInfo = new FileInfo( actual.getAbsolutePath(), Files.readAllBytes( actual.toPath() ) ); + expectedInfo = new FileInfo( expected.getAbsolutePath(), Files.readAllBytes( expected.toPath() ) ); + } + catch ( IOException e ) { + throw new RuntimeException( e ); + } throw Failures.instance() - .failure( info, ShouldHaveSameContent.shouldHaveSameContent( actual, expected, diffs ) ); + .failure( + info, + ShouldHaveSameContent.shouldHaveSameContent( actual, expected, diffs ), + actualInfo, + expectedInfo + ); } } catch ( IOException e ) { 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 index 468e2908a1..eb18615b8b 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/CompilingExtension.java @@ -674,7 +674,7 @@ boolean needsRecompilation(CompilationRequest compilationRequest, CompilationCac return !compilationRequest.equals( compilationCache.getLastRequest() ); } - private static String getBasePath() { + static String getBasePath() { try { return new File( "." ).getCanonicalPath(); } 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 85b6ec80fd..7c6c447d77 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,9 +6,6 @@ package org.mapstruct.ap.testutil.runner; import java.io.File; -import java.net.URL; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -19,6 +16,7 @@ import static org.assertj.core.api.Assertions.fail; import static org.mapstruct.ap.testutil.runner.CompilingExtension.NAMESPACE; +import static org.mapstruct.ap.testutil.runner.CompilingExtension.getBasePath; /** * A {@link org.junit.jupiter.api.extension.RegisterExtension RegisterExtension} to perform assertions on generated @@ -36,6 +34,7 @@ public class GeneratedSource implements BeforeTestExecutionCallback, AfterTestExecutionCallback { private static final String FIXTURES_ROOT = "fixtures/"; + private static final String FIXTURES_DIR = getBasePath() + "/src/test/resources/fixtures/"; /** * ThreadLocal, as the source dir must be injected for this extension to gain access @@ -139,8 +138,8 @@ public JavaFileAssert forJavaFile(String path) { private void handleFixtureComparison() { for ( FixtureComparison fixture : fixturesFor ) { String fixtureName = getFixtureName( fixture.mapperClass, fixture.variant ); - URL expectedFile = getExpectedResource( fixtureName ); - if ( expectedFile == null ) { + File expectedFile = getExpectedResource( fixtureName ); + if ( !expectedFile.exists() ) { fail( String.format( "No reference file could be found for Mapper %s. You should create a file %s", fixture.mapperClass.getName(), @@ -148,8 +147,7 @@ private void handleFixtureComparison() { ) ); } else { - File expectedResource = new File( URLDecoder.decode( expectedFile.getFile(), StandardCharsets.UTF_8 ) ); - forMapper( fixture.mapperClass ).hasSameMapperContent( expectedResource ); + forMapper( fixture.mapperClass ).hasSameMapperContent( expectedFile ); } } } @@ -159,16 +157,15 @@ private String getFixtureName(Class mapperClass, String variant) { return mapperClass.getName().replace( '.', '/' ).concat( suffix ); } - private URL getExpectedResource(String fixtureName) { - ClassLoader classLoader = getClass().getClassLoader(); + private File getExpectedResource(String fixtureName) { 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; + File fixtureFile = new File( FIXTURES_DIR + version + File.separator + fixtureName ); + if ( fixtureFile.exists() ) { + return fixtureFile; } } - return classLoader.getResource( FIXTURES_ROOT + fixtureName ); + return new File( FIXTURES_DIR + fixtureName ); } private static final class FixtureComparison { diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoMapperClassicImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoMapperClassicImpl.java new file mode 100644 index 0000000000..408a83c145 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoMapperClassicImpl.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.nestedbeans; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T22:15:18+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class UserDtoMapperClassicImpl implements UserDtoMapperClassic { + + @Override + public UserDto userToUserDto(User user) { + if ( user == null ) { + return null; + } + + UserDto userDto = new UserDto(); + + userDto.setName( user.getName() ); + userDto.setCar( carToCarDto( user.getCar() ) ); + userDto.setSecondCar( carToCarDto( user.getSecondCar() ) ); + userDto.setHouse( houseToHouseDto( user.getHouse() ) ); + + return userDto; + } + + @Override + public CarDto carToCarDto(Car car) { + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + + carDto.setName( car.getName() ); + carDto.setYear( car.getYear() ); + carDto.setWheels( mapWheels( car.getWheels() ) ); + + return carDto; + } + + @Override + public HouseDto houseToHouseDto(House house) { + if ( house == null ) { + return null; + } + + HouseDto houseDto = new HouseDto(); + + houseDto.setName( house.getName() ); + houseDto.setYear( house.getYear() ); + houseDto.setRoof( roofToRoofDto( house.getRoof() ) ); + + return houseDto; + } + + @Override + public RoofDto roofToRoofDto(Roof roof) { + if ( roof == null ) { + return null; + } + + RoofDto roofDto = new RoofDto(); + + roofDto.setColor( String.valueOf( roof.getColor() ) ); + roofDto.setType( mapRoofType( roof.getType() ) ); + + return roofDto; + } + + @Override + public List mapWheels(List wheels) { + if ( wheels == null ) { + return null; + } + + List list = new ArrayList<>( wheels.size() ); + for ( Wheel wheel : wheels ) { + list.add( mapWheel( wheel ) ); + } + + return list; + } + + @Override + public WheelDto mapWheel(Wheel wheels) { + if ( wheels == null ) { + return null; + } + + WheelDto wheelDto = new WheelDto(); + + wheelDto.setFront( wheels.isFront() ); + wheelDto.setRight( wheels.isRight() ); + + return wheelDto; + } + + @Override + public ExternalRoofType mapRoofType(RoofType roofType) { + if ( roofType == null ) { + return null; + } + + ExternalRoofType externalRoofType = switch ( roofType ) { + case OPEN -> ExternalRoofType.OPEN; + case BOX -> ExternalRoofType.BOX; + case GAMBREL -> ExternalRoofType.GAMBREL; + }; + + return externalRoofType; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoMapperSmartImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoMapperSmartImpl.java new file mode 100644 index 0000000000..f20d3be4ef --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoMapperSmartImpl.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.test.nestedbeans; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T22:22:12+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class UserDtoMapperSmartImpl implements UserDtoMapperSmart { + + @Override + public UserDto userToUserDto(User user) { + if ( user == null ) { + return null; + } + + UserDto userDto = new UserDto(); + + userDto.setName( user.getName() ); + userDto.setCar( carToCarDto( user.getCar() ) ); + userDto.setSecondCar( carToCarDto( user.getSecondCar() ) ); + userDto.setHouse( houseToHouseDto( user.getHouse() ) ); + + return userDto; + } + + @Override + public org.mapstruct.ap.test.nestedbeans.other.UserDto userToUserDto2(User user) { + if ( user == null ) { + return null; + } + + org.mapstruct.ap.test.nestedbeans.other.UserDto userDto = new org.mapstruct.ap.test.nestedbeans.other.UserDto(); + + userDto.setName( user.getName() ); + userDto.setCar( carToCarDto1( user.getCar() ) ); + userDto.setHouse( houseToHouseDto1( user.getHouse() ) ); + + return userDto; + } + + protected WheelDto wheelToWheelDto(Wheel wheel) { + if ( wheel == null ) { + return null; + } + + WheelDto wheelDto = new WheelDto(); + + wheelDto.setFront( wheel.isFront() ); + wheelDto.setRight( wheel.isRight() ); + + return wheelDto; + } + + protected List wheelListToWheelDtoList(List list) { + if ( list == null ) { + return null; + } + + List list1 = new ArrayList<>( list.size() ); + for ( Wheel wheel : list ) { + list1.add( wheelToWheelDto( wheel ) ); + } + + return list1; + } + + protected CarDto carToCarDto(Car car) { + if ( car == null ) { + return null; + } + + CarDto carDto = new CarDto(); + + carDto.setName( car.getName() ); + carDto.setYear( car.getYear() ); + carDto.setWheels( wheelListToWheelDtoList( car.getWheels() ) ); + + return carDto; + } + + protected ExternalRoofType roofTypeToExternalRoofType(RoofType roofType) { + if ( roofType == null ) { + return null; + } + + ExternalRoofType externalRoofType = switch ( roofType ) { + case OPEN -> ExternalRoofType.OPEN; + case BOX -> ExternalRoofType.BOX; + case GAMBREL -> ExternalRoofType.GAMBREL; + }; + + return externalRoofType; + } + + protected RoofDto roofToRoofDto(Roof roof) { + if ( roof == null ) { + return null; + } + + RoofDto roofDto = new RoofDto(); + + roofDto.setColor( String.valueOf( roof.getColor() ) ); + roofDto.setType( roofTypeToExternalRoofType( roof.getType() ) ); + + return roofDto; + } + + protected HouseDto houseToHouseDto(House house) { + if ( house == null ) { + return null; + } + + HouseDto houseDto = new HouseDto(); + + houseDto.setName( house.getName() ); + houseDto.setYear( house.getYear() ); + houseDto.setRoof( roofToRoofDto( house.getRoof() ) ); + + return houseDto; + } + + protected org.mapstruct.ap.test.nestedbeans.other.WheelDto wheelToWheelDto1(Wheel wheel) { + if ( wheel == null ) { + return null; + } + + org.mapstruct.ap.test.nestedbeans.other.WheelDto wheelDto = new org.mapstruct.ap.test.nestedbeans.other.WheelDto(); + + wheelDto.setFront( wheel.isFront() ); + wheelDto.setRight( wheel.isRight() ); + + return wheelDto; + } + + protected List wheelListToWheelDtoList1(List list) { + if ( list == null ) { + return null; + } + + List list1 = new ArrayList<>( list.size() ); + for ( Wheel wheel : list ) { + list1.add( wheelToWheelDto1( wheel ) ); + } + + return list1; + } + + protected org.mapstruct.ap.test.nestedbeans.other.CarDto carToCarDto1(Car car) { + if ( car == null ) { + return null; + } + + org.mapstruct.ap.test.nestedbeans.other.CarDto carDto = new org.mapstruct.ap.test.nestedbeans.other.CarDto(); + + carDto.setName( car.getName() ); + carDto.setYear( car.getYear() ); + carDto.setWheels( wheelListToWheelDtoList1( car.getWheels() ) ); + + return carDto; + } + + protected org.mapstruct.ap.test.nestedbeans.other.RoofDto roofToRoofDto1(Roof roof) { + if ( roof == null ) { + return null; + } + + org.mapstruct.ap.test.nestedbeans.other.RoofDto roofDto = new org.mapstruct.ap.test.nestedbeans.other.RoofDto(); + + roofDto.setColor( String.valueOf( roof.getColor() ) ); + + return roofDto; + } + + protected org.mapstruct.ap.test.nestedbeans.other.HouseDto houseToHouseDto1(House house) { + if ( house == null ) { + return null; + } + + org.mapstruct.ap.test.nestedbeans.other.HouseDto houseDto = new org.mapstruct.ap.test.nestedbeans.other.HouseDto(); + + houseDto.setName( house.getName() ); + houseDto.setYear( house.getYear() ); + houseDto.setRoof( roofToRoofDto1( house.getRoof() ) ); + + return houseDto; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoUpdateMapperSmartImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoUpdateMapperSmartImpl.java new file mode 100644 index 0000000000..1b82ddc4ee --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nestedbeans/UserDtoUpdateMapperSmartImpl.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.test.nestedbeans; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T22:22:13+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class UserDtoUpdateMapperSmartImpl implements UserDtoUpdateMapperSmart { + + @Override + public void userToUserDto(UserDto userDto, User user) { + if ( user == null ) { + return; + } + + if ( user.getName() != null ) { + userDto.setName( user.getName() ); + } + else { + userDto.setName( null ); + } + if ( user.getCar() != null ) { + if ( userDto.getCar() == null ) { + userDto.setCar( new CarDto() ); + } + carToCarDto( user.getCar(), userDto.getCar() ); + } + else { + userDto.setCar( null ); + } + if ( user.getSecondCar() != null ) { + if ( userDto.getSecondCar() == null ) { + userDto.setSecondCar( new CarDto() ); + } + carToCarDto( user.getSecondCar(), userDto.getSecondCar() ); + } + else { + userDto.setSecondCar( null ); + } + if ( user.getHouse() != null ) { + if ( userDto.getHouse() == null ) { + userDto.setHouse( new HouseDto() ); + } + houseToHouseDto( user.getHouse(), userDto.getHouse() ); + } + else { + userDto.setHouse( null ); + } + } + + protected WheelDto wheelToWheelDto(Wheel wheel) { + if ( wheel == null ) { + return null; + } + + WheelDto wheelDto = new WheelDto(); + + wheelDto.setFront( wheel.isFront() ); + wheelDto.setRight( wheel.isRight() ); + + return wheelDto; + } + + protected List wheelListToWheelDtoList(List list) { + if ( list == null ) { + return null; + } + + List list1 = new ArrayList<>( list.size() ); + for ( Wheel wheel : list ) { + list1.add( wheelToWheelDto( wheel ) ); + } + + return list1; + } + + protected void carToCarDto(Car car, CarDto mappingTarget) { + if ( car == null ) { + return; + } + + if ( car.getName() != null ) { + mappingTarget.setName( car.getName() ); + } + else { + mappingTarget.setName( null ); + } + mappingTarget.setYear( car.getYear() ); + if ( mappingTarget.getWheels() != null ) { + List list = wheelListToWheelDtoList( car.getWheels() ); + if ( list != null ) { + mappingTarget.getWheels().clear(); + mappingTarget.getWheels().addAll( list ); + } + } + else { + List list = wheelListToWheelDtoList( car.getWheels() ); + if ( list != null ) { + mappingTarget.setWheels( list ); + } + } + } + + protected ExternalRoofType roofTypeToExternalRoofType(RoofType roofType) { + if ( roofType == null ) { + return null; + } + + ExternalRoofType externalRoofType = switch ( roofType ) { + case OPEN -> ExternalRoofType.OPEN; + case BOX -> ExternalRoofType.BOX; + case GAMBREL -> ExternalRoofType.GAMBREL; + }; + + return externalRoofType; + } + + protected void roofToRoofDto(Roof roof, RoofDto mappingTarget) { + if ( roof == null ) { + return; + } + + mappingTarget.setColor( String.valueOf( roof.getColor() ) ); + if ( roof.getType() != null ) { + mappingTarget.setType( roofTypeToExternalRoofType( roof.getType() ) ); + } + else { + mappingTarget.setType( null ); + } + } + + protected void houseToHouseDto(House house, HouseDto mappingTarget) { + if ( house == null ) { + return; + } + + if ( house.getName() != null ) { + mappingTarget.setName( house.getName() ); + } + else { + mappingTarget.setName( null ); + } + mappingTarget.setYear( house.getYear() ); + if ( house.getRoof() != null ) { + if ( mappingTarget.getRoof() == null ) { + mappingTarget.setRoof( new RoofDto() ); + } + roofToRoofDto( house.getRoof(), mappingTarget.getRoof() ); + } + else { + mappingTarget.setRoof( null ); + } + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapperImpl.java new file mode 100644 index 0000000000..a655a662dc --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapperImpl.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.nullcheck.jspecify; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-14T23:31:18+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyMapMethodMapperImpl implements JSpecifyMapMethodMapper { + + @Override + public Map mapAll(Map sources) { + + Map map = LinkedHashMap.newLinkedHashMap( sources.size() ); + + for ( java.util.Map.Entry entry : sources.entrySet() ) { + String key = entry.getKey(); + NullMarkedTargetBean value = map( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapperImpl.java new file mode 100644 index 0000000000..eefeafd349 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapperImpl.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.nullcheck.jspecify; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-15T00:03:32+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyNonNullReturnMapMapperImpl implements JSpecifyNonNullReturnMapMapper { + + @Override + public Map mapAll(Map sources) { + if ( sources == null ) { + return new LinkedHashMap<>(); + } + + Map map = LinkedHashMap.newLinkedHashMap( sources.size() ); + + for ( java.util.Map.Entry entry : sources.entrySet() ) { + String key = entry.getKey(); + NullMarkedTargetBean value = map( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/DefaultOrderMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/DefaultOrderMapperImpl.java new file mode 100644 index 0000000000..864accaa9e --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/DefaultOrderMapperImpl.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.value.enum2enum; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-05T22:27:58+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class DefaultOrderMapperImpl implements DefaultOrderMapper { + + @Override + public OrderDto orderEntityToDto(OrderEntity order) { + if ( order == null ) { + return null; + } + + OrderDto orderDto = new OrderDto(); + + orderDto.setOrderType( orderTypeToExternalOrderType( order.getOrderType() ) ); + + return orderDto; + } + + @Override + public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = ExternalOrderType.DEFAULT; + + return externalOrderType; + } +} \ No newline at end of file diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/OrderMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/OrderMapperImpl.java new file mode 100644 index 0000000000..61e6bb3f03 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/OrderMapperImpl.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.value.enum2enum; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T14:19:13+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class OrderMapperImpl implements OrderMapper { + + @Override + public OrderDto orderEntityToDto(OrderEntity order) { + if ( order == null ) { + return null; + } + + OrderDto orderDto = new OrderDto(); + + orderDto.setOrderType( orderTypeToExternalOrderType( order.getOrderType() ) ); + + return orderDto; + } + + @Override + public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case EXTRA -> ExternalOrderType.SPECIAL; + case STANDARD -> ExternalOrderType.DEFAULT; + case NORMAL -> ExternalOrderType.DEFAULT; + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + }; + + return externalOrderType; + } + + @Override + public OrderType externalOrderTypeToOrderType(ExternalOrderType orderType) { + if ( orderType == null ) { + return null; + } + + OrderType orderType1 = switch ( orderType ) { + case SPECIAL -> OrderType.EXTRA; + case DEFAULT -> OrderType.STANDARD; + case RETAIL -> OrderType.RETAIL; + case B2B -> OrderType.B2B; + }; + + return orderType1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/SpecialOrderMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/SpecialOrderMapperImpl.java new file mode 100644 index 0000000000..223485c327 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/enum2enum/SpecialOrderMapperImpl.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.value.enum2enum; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T16:23:42+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class SpecialOrderMapperImpl implements SpecialOrderMapper { + + @Override + public OrderDto orderEntityToDto(OrderEntity order) { + if ( order == null ) { + return null; + } + + OrderDto orderDto = new OrderDto(); + + orderDto.setOrderType( orderTypeToExternalOrderType( order.getOrderType() ) ); + + return orderDto; + } + + @Override + public ExternalOrderType orderTypeToExternalOrderType(OrderType orderType) { + if ( orderType == null ) { + return ExternalOrderType.DEFAULT; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case STANDARD -> null; + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + default -> ExternalOrderType.SPECIAL; + }; + + return externalOrderType; + } + + @Override + public OrderType externalOrderTypeToOrderType(ExternalOrderType orderType) { + if ( orderType == null ) { + return OrderType.STANDARD; + } + + OrderType orderType1 = switch ( orderType ) { + case SPECIAL -> OrderType.EXTRA; + case DEFAULT -> null; + case RETAIL -> OrderType.RETAIL; + case B2B -> OrderType.B2B; + }; + + return orderType1; + } + + @Override + public ExternalOrderType anyRemainingToNull(OrderType orderType) { + if ( orderType == null ) { + return ExternalOrderType.DEFAULT; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case STANDARD -> null; + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + default -> null; + }; + + return externalOrderType; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperAndEnumMappingImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperAndEnumMappingImpl.java new file mode 100644 index 0000000000..bfa7fc1fdc --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperAndEnumMappingImpl.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.value.exception; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T16:23:39+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class CustomUnexpectedValueMappingExceptionDefinedInMapperAndEnumMappingImpl implements CustomUnexpectedValueMappingExceptionDefinedInMapperAndEnumMapping { + + @Override + public ExternalOrderType withAnyUnmapped(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = ExternalOrderType.DEFAULT; + + return externalOrderType; + } + + @Override + public ExternalOrderType withAnyRemaining(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + default -> ExternalOrderType.DEFAULT; + }; + + return externalOrderType; + } + + @Override + public ExternalOrderType onlyWithMappings(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case EXTRA -> ExternalOrderType.SPECIAL; + case STANDARD -> ExternalOrderType.DEFAULT; + case NORMAL -> ExternalOrderType.DEFAULT; + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + }; + + return externalOrderType; + } + + @Override + public OrderType inverseOnlyWithMappings(ExternalOrderType orderType) { + if ( orderType == null ) { + return null; + } + + OrderType orderType1 = switch ( orderType ) { + case SPECIAL -> OrderType.EXTRA; + case DEFAULT -> OrderType.STANDARD; + case RETAIL -> OrderType.RETAIL; + case B2B -> OrderType.B2B; + }; + + return orderType1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperConfigImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperConfigImpl.java new file mode 100644 index 0000000000..641ff95b17 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperConfigImpl.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.value.exception; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T22:01:43+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class CustomUnexpectedValueMappingExceptionDefinedInMapperConfigImpl implements CustomUnexpectedValueMappingExceptionDefinedInMapperConfig { + + @Override + public ExternalOrderType withAnyUnmapped(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = ExternalOrderType.DEFAULT; + + return externalOrderType; + } + + @Override + public ExternalOrderType withAnyRemaining(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + default -> ExternalOrderType.DEFAULT; + }; + + return externalOrderType; + } + + @Override + public ExternalOrderType onlyWithMappings(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case EXTRA -> ExternalOrderType.SPECIAL; + case STANDARD -> ExternalOrderType.DEFAULT; + case NORMAL -> ExternalOrderType.DEFAULT; + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + }; + + return externalOrderType; + } + + @Override + public OrderType inverseOnlyWithMappings(ExternalOrderType orderType) { + if ( orderType == null ) { + return null; + } + + OrderType orderType1 = switch ( orderType ) { + case SPECIAL -> OrderType.EXTRA; + case DEFAULT -> OrderType.STANDARD; + case RETAIL -> OrderType.RETAIL; + case B2B -> OrderType.B2B; + }; + + return orderType1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperImpl.java new file mode 100644 index 0000000000..2f47b74ae4 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionDefinedInMapperImpl.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.value.exception; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T14:19:13+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class CustomUnexpectedValueMappingExceptionDefinedInMapperImpl implements CustomUnexpectedValueMappingExceptionDefinedInMapper { + + @Override + public ExternalOrderType withAnyUnmapped(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = ExternalOrderType.DEFAULT; + + return externalOrderType; + } + + @Override + public ExternalOrderType withAnyRemaining(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + default -> ExternalOrderType.DEFAULT; + }; + + return externalOrderType; + } + + @Override + public ExternalOrderType onlyWithMappings(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case EXTRA -> ExternalOrderType.SPECIAL; + case STANDARD -> ExternalOrderType.DEFAULT; + case NORMAL -> ExternalOrderType.DEFAULT; + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + }; + + return externalOrderType; + } + + @Override + public OrderType inverseOnlyWithMappings(ExternalOrderType orderType) { + if ( orderType == null ) { + return null; + } + + OrderType orderType1 = switch ( orderType ) { + case SPECIAL -> OrderType.EXTRA; + case DEFAULT -> OrderType.STANDARD; + case RETAIL -> OrderType.RETAIL; + case B2B -> OrderType.B2B; + }; + + return orderType1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionMapperImpl.java new file mode 100644 index 0000000000..ef5c3f9bfa --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/exception/CustomUnexpectedValueMappingExceptionMapperImpl.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.value.exception; + +import javax.annotation.processing.Generated; +import org.mapstruct.ap.test.value.ExternalOrderType; +import org.mapstruct.ap.test.value.OrderType; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T22:04:26+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class CustomUnexpectedValueMappingExceptionMapperImpl implements CustomUnexpectedValueMappingExceptionMapper { + + @Override + public ExternalOrderType withAnyUnmapped(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = ExternalOrderType.DEFAULT; + + return externalOrderType; + } + + @Override + public ExternalOrderType withAnyRemaining(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + default -> ExternalOrderType.DEFAULT; + }; + + return externalOrderType; + } + + @Override + public ExternalOrderType onlyWithMappings(OrderType orderType) { + if ( orderType == null ) { + return null; + } + + ExternalOrderType externalOrderType = switch ( orderType ) { + case EXTRA -> ExternalOrderType.SPECIAL; + case STANDARD -> ExternalOrderType.DEFAULT; + case NORMAL -> ExternalOrderType.DEFAULT; + case RETAIL -> ExternalOrderType.RETAIL; + case B2B -> ExternalOrderType.B2B; + }; + + return externalOrderType; + } + + @Override + public OrderType inverseOnlyWithMappings(ExternalOrderType orderType) { + if ( orderType == null ) { + return null; + } + + OrderType orderType1 = switch ( orderType ) { + case SPECIAL -> OrderType.EXTRA; + case DEFAULT -> OrderType.STANDARD; + case RETAIL -> OrderType.RETAIL; + case B2B -> OrderType.B2B; + }; + + return orderType1; + } +} diff --git a/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/spi/CustomCheeseMapperImpl.java b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/spi/CustomCheeseMapperImpl.java new file mode 100644 index 0000000000..94ac662859 --- /dev/null +++ b/processor/src/test/resources/fixtures/21/org/mapstruct/ap/test/value/spi/CustomCheeseMapperImpl.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.value.spi; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-12T14:19:17+0200", + comments = "version: , compiler: javac, environment: Java 21.0.11 (Eclipse Adoptium)" +) +public class CustomCheeseMapperImpl implements CustomCheeseMapper { + + @Override + public CheeseType map(CustomCheeseType cheese) { + if ( cheese == null ) { + return null; + } + + CheeseType cheeseType = switch ( cheese ) { + case UNSPECIFIED -> null; + case CUSTOM_BRIE -> CheeseType.BRIE; + case CUSTOM_ROQUEFORT -> CheeseType.ROQUEFORT; + case UNRECOGNIZED -> null; + }; + + return cheeseType; + } + + @Override + public CustomCheeseType map(CheeseType cheese) { + if ( cheese == null ) { + return CustomCheeseType.UNSPECIFIED; + } + + CustomCheeseType customCheeseType = switch ( cheese ) { + case BRIE -> CustomCheeseType.CUSTOM_BRIE; + case ROQUEFORT -> CustomCheeseType.CUSTOM_ROQUEFORT; + }; + + return customCheeseType; + } + + @Override + public String mapToString(CustomCheeseType cheeseType) { + if ( cheeseType == null ) { + return null; + } + + String string = switch ( cheeseType ) { + case UNSPECIFIED -> null; + case CUSTOM_BRIE -> "BRIE"; + case CUSTOM_ROQUEFORT -> "ROQUEFORT"; + case UNRECOGNIZED -> null; + }; + + return string; + } + + @Override + public String mapToString(CheeseType cheeseType) { + if ( cheeseType == null ) { + return null; + } + + String string = switch ( cheeseType ) { + case BRIE -> "BRIE"; + case ROQUEFORT -> "ROQUEFORT"; + }; + + return string; + } + + @Override + public CustomCheeseType mapStringToCustom(String cheese) { + if ( cheese == null ) { + return CustomCheeseType.UNSPECIFIED; + } + + CustomCheeseType customCheeseType = switch ( cheese ) { + case "BRIE" -> CustomCheeseType.CUSTOM_BRIE; + case "ROQUEFORT" -> CustomCheeseType.CUSTOM_ROQUEFORT; + default -> CustomCheeseType.CUSTOM_BRIE; + }; + + return customCheeseType; + } + + @Override + public CheeseType mapStringToCheese(String cheese) { + if ( cheese == null ) { + return null; + } + + CheeseType cheeseType = switch ( cheese ) { + case "BRIE" -> CheeseType.BRIE; + case "ROQUEFORT" -> CheeseType.ROQUEFORT; + default -> CheeseType.BRIE; + }; + + return cheeseType; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapperImpl.java new file mode 100644 index 0000000000..71f9998364 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/conversion/java8time/zonedoffsetdatetimetolocaldatetimeconversion/SourceTargetMapperImpl.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.conversion.java8time.zonedoffsetdatetimetolocaldatetimeconversion; + +import java.time.ZoneOffset; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-23T21:55:42+0900", + comments = "version: , compiler: javac, environment: Java 25.0.2 (Homebrew)" +) +public class SourceTargetMapperImpl implements SourceTargetMapper { + + @Override + public Target toTarget(Source source) { + if ( source == null ) { + return null; + } + + Target target = new Target(); + + if ( source.getOffsetDateTime() != null ) { + target.setOffsetDateTime( source.getOffsetDateTime().toLocalDateTime() ); + } + if ( source.getZonedDateTime() != null ) { + target.setZonedDateTime( source.getZonedDateTime().toLocalDateTime() ); + } + if ( source.getZonedDateTimeAsInstant() != null ) { + target.setZonedDateTimeAsInstant( source.getZonedDateTimeAsInstant().toInstant() ); + } + if ( source.getOffsetDateTimeAsInstant() != null ) { + target.setOffsetDateTimeAsInstant( source.getOffsetDateTimeAsInstant().toInstant() ); + } + + return target; + } + + @Override + public Source toSource(Target target) { + if ( target == null ) { + return null; + } + + Source source = new Source(); + + if ( target.getZonedDateTime() != null ) { + source.setZonedDateTime( target.getZonedDateTime().atZone( ZoneOffset.UTC ) ); + } + if ( target.getOffsetDateTime() != null ) { + source.setOffsetDateTime( target.getOffsetDateTime().atOffset( ZoneOffset.UTC ) ); + } + if ( target.getZonedDateTimeAsInstant() != null ) { + source.setZonedDateTimeAsInstant( target.getZonedDateTimeAsInstant().atZone( ZoneOffset.UTC ) ); + } + if ( target.getOffsetDateTimeAsInstant() != null ) { + source.setOffsetDateTimeAsInstant( target.getOffsetDateTimeAsInstant().atOffset( ZoneOffset.UTC ) ); + } + + return source; + } +} \ No newline at end of file diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyMapperImpl.java new file mode 100644 index 0000000000..4004bc60fe --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyCollectionPropertyMapperImpl.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.nullcheck.jspecify; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-14T23:05:10+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyCollectionPropertyMapperImpl implements JSpecifyCollectionPropertyMapper { + + @Override + public NullMarkedCollectionTargetBean map(NullMarkedCollectionSourceBean source) { + + NullMarkedCollectionTargetBean nullMarkedCollectionTargetBean = new NullMarkedCollectionTargetBean(); + + nullMarkedCollectionTargetBean.setValues( source.getValues() ); + + return nullMarkedCollectionTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodMapperImpl.java new file mode 100644 index 0000000000..5336db031a --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyIterableMethodMapperImpl.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.nullcheck.jspecify; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-14T23:18:22+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyIterableMethodMapperImpl implements JSpecifyIterableMethodMapper { + + @Override + public List mapAll(List sources) { + + List list = new ArrayList<>( sources.size() ); + for ( NullMarkedSourceBean nullMarkedSourceBean : sources ) { + list.add( map( nullMarkedSourceBean ) ); + } + + return list; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapperImpl.java new file mode 100644 index 0000000000..2a74b75c6e --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyMapMethodMapperImpl.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.nullcheck.jspecify; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-14T23:30:08+0200", + comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyMapMethodMapperImpl implements JSpecifyMapMethodMapper { + + @Override + public Map mapAll(Map sources) { + + Map map = new LinkedHashMap<>( Math.max( (int) ( sources.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : sources.entrySet() ) { + String key = entry.getKey(); + NullMarkedTargetBean value = map( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanMapperImpl.java new file mode 100644 index 0000000000..c1efd39d0d --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnBeanMapperImpl.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.nullcheck.jspecify; + +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-14T23:41:40+0200", + comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyNonNullReturnBeanMapperImpl implements JSpecifyNonNullReturnBeanMapper { + + @Override + public NullMarkedTargetBean map(JSpecifyNonNullReturnBeanSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + if ( source != null ) { + nullMarkedTargetBean.setNonNullByDefault( source.getValue() ); + } + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableMapperImpl.java new file mode 100644 index 0000000000..8c6de34e37 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnIterableMapperImpl.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.nullcheck.jspecify; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-14T23:55:43+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyNonNullReturnIterableMapperImpl implements JSpecifyNonNullReturnIterableMapper { + + @Override + public List mapAll(List sources) { + if ( sources == null ) { + return new ArrayList<>(); + } + + List list = new ArrayList<>( sources.size() ); + for ( NullMarkedSourceBean nullMarkedSourceBean : sources ) { + list.add( map( nullMarkedSourceBean ) ); + } + + return list; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapperImpl.java new file mode 100644 index 0000000000..ffd45d3855 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnMapMapperImpl.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.nullcheck.jspecify; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-05-15T00:03:33+0200", + comments = "version: , compiler: Eclipse JDT (Batch) 3.20.0.v20191203-2131, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyNonNullReturnMapMapperImpl implements JSpecifyNonNullReturnMapMapper { + + @Override + public Map mapAll(Map sources) { + if ( sources == null ) { + return new LinkedHashMap<>(); + } + + Map map = new LinkedHashMap<>( Math.max( (int) ( sources.size() / .75f ) + 1, 16 ) ); + + for ( java.util.Map.Entry entry : sources.entrySet() ) { + String key = entry.getKey(); + NullMarkedTargetBean value = map( entry.getValue() ); + map.put( key, value ); + } + + return map; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamMapperImpl.java new file mode 100644 index 0000000000..01c2981ab1 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnStreamMapperImpl.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.nullcheck.jspecify; + +import java.util.stream.Stream; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-05T14:55:49+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyNonNullReturnStreamMapperImpl implements JSpecifyNonNullReturnStreamMapper { + + @Override + public Stream mapAll(Stream sources) { + if ( sources == null ) { + return Stream.empty(); + } + + return sources.map( nullMarkedSourceBean -> map( nullMarkedSourceBean ) ); + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableMapperImpl.java new file mode 100644 index 0000000000..70b8fbea1d --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNonNullReturnUpdateIterableMapperImpl.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.nullcheck.jspecify; + +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-05T15:01:46+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyNonNullReturnUpdateIterableMapperImpl implements JSpecifyNonNullReturnUpdateIterableMapper { + + @Override + public List mapAll(List sources, List target) { + if ( sources == null ) { + return target; + } + + target.clear(); + for ( NullMarkedSourceBean nullMarkedSourceBean : sources ) { + target.add( map( nullMarkedSourceBean ) ); + } + + return target; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableMapperImpl.java new file mode 100644 index 0000000000..2a6950f71d --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyNullableSourceIterableMapperImpl.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.nullcheck.jspecify; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-05T14:55:49+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyNullableSourceIterableMapperImpl implements JSpecifyNullableSourceIterableMapper { + + @Override + public List mapAll(List sources) { + if ( sources == null ) { + return null; + } + + List list = new ArrayList<>( sources.size() ); + for ( NullMarkedSourceBean nullMarkedSourceBean : sources ) { + list.add( map( nullMarkedSourceBean ) ); + } + + return list; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableMapperImpl.java new file mode 100644 index 0000000000..52313a0327 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyReturnNullOverrideIterableMapperImpl.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.nullcheck.jspecify; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-05T14:55:49+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyReturnNullOverrideIterableMapperImpl implements JSpecifyReturnNullOverrideIterableMapper { + + @Override + public List mapAll(List sources) { + if ( sources == null ) { + return new ArrayList<>(); + } + + List list = new ArrayList<>( sources.size() ); + for ( NullMarkedSourceBean nullMarkedSourceBean : sources ) { + list.add( map( nullMarkedSourceBean ) ); + } + + return list; + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +} diff --git a/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodMapperImpl.java b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodMapperImpl.java new file mode 100644 index 0000000000..4f276edc03 --- /dev/null +++ b/processor/src/test/resources/fixtures/org/mapstruct/ap/test/nullcheck/jspecify/JSpecifyStreamMethodMapperImpl.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.nullcheck.jspecify; + +import java.util.stream.Stream; +import javax.annotation.processing.Generated; + +@Generated( + value = "org.mapstruct.ap.MappingProcessor", + date = "2026-06-05T14:54:43+0200", + comments = "version: , compiler: javac, environment: Java 25 (Eclipse Adoptium)" +) +public class JSpecifyStreamMethodMapperImpl implements JSpecifyStreamMethodMapper { + + @Override + public Stream mapAll(Stream sources) { + + return sources.map( nullMarkedSourceBean -> map( nullMarkedSourceBean ) ); + } + + @Override + public NullMarkedTargetBean map(NullMarkedSourceBean source) { + + NullMarkedTargetBean nullMarkedTargetBean = new NullMarkedTargetBean(); + + nullMarkedTargetBean.setNonNullByDefault( source.getNonNullByDefault() ); + nullMarkedTargetBean.setExplicitlyNullable( source.getExplicitlyNullable() ); + + return nullMarkedTargetBean; + } +}