From 2c84d04463a3ec2edd6c05e726a579b4ba936847 Mon Sep 17 00:00:00 2001 From: Cause Chung Date: Sun, 11 May 2025 17:33:35 +0200 Subject: [PATCH] #3240 Add Support for Java21 SequencedSet and SequencedMap --- .github/workflows/main.yml | 24 +++--- .github/workflows/release.yml | 2 +- .../chapter-6-mapping-collections.asciidoc | 4 + ...eatureCompilationExclusionCliEnhancer.java | 7 ++ .../test/resources/fullFeatureTest/pom.xml | 2 + parent/pom.xml | 2 +- processor/pom.xml | 2 +- .../ap/internal/model/common/TypeFactory.java | 13 ++++ .../util/JavaCollectionConstants.java | 21 ++++++ ...dCollectionsDefaultImplementationTest.java | 74 +++++++++++++++++++ .../jdk21/SequencedCollectionsMapper.java | 26 +++++++ readme.md | 3 +- 12 files changed, 164 insertions(+), 16 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a760175c0..166466348 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - java: [17, 21] + java: [21] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: @@ -24,31 +24,31 @@ jobs: distribution: 'zulu' java-version: ${{ matrix.java }} - name: 'Test' - run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 17 }} install -DskipDistribution=${{ matrix.java != 17 }} + run: ./mvnw ${MAVEN_ARGS} -Djacoco.skip=${{ matrix.java != 21 }} install -DskipDistribution=${{ matrix.java != 21 }} - name: 'Generate coverage report' - if: matrix.java == 17 + if: matrix.java == 21 run: ./mvnw jacoco:report - name: 'Upload coverage to Codecov' - if: matrix.java == 17 + if: matrix.java == 21 uses: codecov/codecov-action@v2 - name: 'Publish Snapshots' - if: matrix.java == 17 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' + if: matrix.java == 21 && github.event_name == 'push' && github.ref == 'refs/heads/main' && github.repository == 'mapstruct/mapstruct' run: ./mvnw -s etc/ci-settings.xml -DskipTests=true -DskipDistribution=true deploy integration_test_jdk: strategy: fail-fast: false matrix: - java: [ 8, 11 ] + java: [ 8, 11, 17 ] name: 'Linux JDK ${{ matrix.java }}' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@v4 - - name: 'Set up JDK 17 for building everything' + - name: 'Set up JDK 21 for building everything' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + 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' @@ -63,11 +63,11 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v4 - - name: 'Set up JDK 17' + - name: 'Set up JDK 21' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: 'Test' run: ./mvnw %MAVEN_ARGS% install mac: @@ -75,10 +75,10 @@ jobs: runs-on: macos-latest steps: - uses: actions/checkout@v3 - - name: 'Set up JDK 17' + - name: 'Set up JDK 21' uses: actions/setup-java@v4 with: distribution: 'zulu' - java-version: 17 + java-version: 21 - name: 'Test' run: ./mvnw ${MAVEN_ARGS} install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 682fa3687..61f347af3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'zulu' cache: maven diff --git a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc index 79d4544f9..4510c82cc 100644 --- a/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc +++ b/documentation/src/main/asciidoc/chapter-6-mapping-collections.asciidoc @@ -212,12 +212,16 @@ When an iterable or map mapping method declares an interface type as return type |`Set`|`LinkedHashSet` +|`SequencedSet`|`LinkedHashSet` + |`SortedSet`|`TreeSet` |`NavigableSet`|`TreeSet` |`Map`|`LinkedHashMap` +|`SequencedMap`|`LinkedHashMap` + |`SortedMap`|`TreeMap` |`NavigableMap`|`TreeMap` diff --git a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java index 77a5e2af0..0cc1a8781 100644 --- a/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java +++ b/integrationtest/src/test/java/org/mapstruct/itest/tests/FullFeatureCompilationExclusionCliEnhancer.java @@ -34,6 +34,7 @@ public final class FullFeatureCompilationExclusionCliEnhancer implements Process additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/injectionstrategy/jakarta_cdi/**/*.java" ); additionalExcludes.add( "org/mapstruct/ap/test/annotatewith/deprecated/jdk11/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); if ( processorType == ProcessorTest.ProcessorType.ECLIPSE_JDT ) { additionalExcludes.add( "org/mapstruct/ap/test/selection/methodgenerics/wildcards/LifecycleIntersectionMapper.java" ); @@ -42,9 +43,15 @@ public final class FullFeatureCompilationExclusionCliEnhancer implements Process case JAVA_9: // TODO find out why this fails: additionalExcludes.add( "org/mapstruct/ap/test/collection/wildcard/BeanMapper.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); break; case JAVA_11: additionalExcludes.add( "org/mapstruct/ap/test/**/spring/**/*.java" ); + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + break; + case JAVA_17: + additionalExcludes.add( "org/mapstruct/ap/test/**/jdk21/*.java" ); + break; default: } diff --git a/integrationtest/src/test/resources/fullFeatureTest/pom.xml b/integrationtest/src/test/resources/fullFeatureTest/pom.xml index 849f7150f..b5ddae9a7 100644 --- a/integrationtest/src/test/resources/fullFeatureTest/pom.xml +++ b/integrationtest/src/test/resources/fullFeatureTest/pom.xml @@ -59,6 +59,8 @@ ${additionalExclude9} ${additionalExclude10} ${additionalExclude11} + ${additionalExclude12} + ${additionalExclude13} diff --git a/parent/pom.xml b/parent/pom.xml index 5735d4760..f6ad60a22 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -47,7 +47,7 @@ jdt_apt 1.8 3.25.5 diff --git a/processor/pom.xml b/processor/pom.xml index 13deafb41..2341c4ddb 100644 --- a/processor/pom.xml +++ b/processor/pom.xml @@ -24,7 +24,7 @@ - 17 + 21 diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index ceb190a33..c2646f63d 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -45,6 +45,7 @@ import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.ElementUtils; import org.mapstruct.ap.internal.util.Extractor; import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.JavaCollectionConstants; import org.mapstruct.ap.internal.util.JavaStreamConstants; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.NativeTypes; @@ -149,6 +150,18 @@ public class TypeFactory { ConcurrentNavigableMap.class.getName(), withDefaultConstructor( getType( ConcurrentSkipListMap.class ) ) ); + implementationTypes.put( + JavaCollectionConstants.SEQUENCED_SET_FQN, + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashSet.class ), LINKED_HASH_SET_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashSet.class ) ) + ); + implementationTypes.put( + JavaCollectionConstants.SEQUENCED_MAP_FQN, + sourceVersionAtLeast19 ? + withFactoryMethod( getType( LinkedHashMap.class ), LINKED_HASH_MAP_FACTORY_METHOD_NAME ) : + withLoadFactorAdjustment( getType( LinkedHashMap.class ) ) + ); this.loggingVerbose = loggingVerbose; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java new file mode 100644 index 000000000..68d556c54 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/JavaCollectionConstants.java @@ -0,0 +1,21 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.internal.util; + +/** + * Helper holding Java collections full qualified class names for conversion registration, + * to achieve Java compatibility. + * + * @author Cause Chung + */ +public final class JavaCollectionConstants { + public static final String SEQUENCED_MAP_FQN = "java.util.SequencedMap"; + public static final String SEQUENCED_SET_FQN = "java.util.SequencedSet"; + + private JavaCollectionConstants() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java new file mode 100644 index 000000000..4551d5b0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsDefaultImplementationTest.java @@ -0,0 +1,74 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.defaultimplementation.jdk21; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.ap.test.collection.defaultimplementation.Source; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.Target; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +import org.mapstruct.ap.testutil.WithClasses; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +@WithClasses({ + Source.class, + Target.class, + SourceFoo.class, + TargetFoo.class, + SequencedCollectionsMapper.class +}) +@IssueKey("3420") +class SequencedCollectionsDefaultImplementationTest { + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedMap() { + SequencedMap target = + SequencedCollectionsMapper.INSTANCE.sourceFooMapToTargetFooSequencedMap( createSourceFooMap() ); + + assertResultMap( target ); + } + + @ProcessorTest + public void shouldUseDefaultImplementationForSequencedSet() { + SequencedSet target = + SequencedCollectionsMapper.INSTANCE.sourceFoosToTargetFooSequencedSet( createSourceFooList() ); + + assertResultList( target ); + } + + private void assertResultList(Iterable fooIterable) { + assertThat( fooIterable ).isNotNull(); + assertThat( fooIterable ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); + } + + private void assertResultMap(Map result) { + assertThat( result ).isNotNull(); + assertThat( result ).hasSize( 2 ); + assertThat( result ).contains( entry( "1", new TargetFoo( "Bob" ) ), entry( "2", new TargetFoo( "Alice" ) ) ); + } + + private Map createSourceFooMap() { + Map map = new HashMap<>(); + map.put( 1L, new SourceFoo( "Bob" ) ); + map.put( 2L, new SourceFoo( "Alice" ) ); + + return map; + } + + private List createSourceFooList() { + return Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java new file mode 100644 index 000000000..bbffb56b0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/jdk21/SequencedCollectionsMapper.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.collection.defaultimplementation.jdk21; + +import java.util.Collection; +import java.util.Map; +import java.util.SequencedMap; +import java.util.SequencedSet; + +import org.mapstruct.Mapper; +import org.mapstruct.ap.test.collection.defaultimplementation.SourceFoo; +import org.mapstruct.ap.test.collection.defaultimplementation.TargetFoo; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SequencedCollectionsMapper { + + SequencedCollectionsMapper INSTANCE = Mappers.getMapper( SequencedCollectionsMapper.class ); + + SequencedSet sourceFoosToTargetFooSequencedSet(Collection foos); + + SequencedMap sourceFooMapToTargetFooSequencedMap(Map foos); +} diff --git a/readme.md b/readme.md index 907c8411c..16a70f3f0 100644 --- a/readme.md +++ b/readme.md @@ -130,7 +130,8 @@ To learn more about MapStruct, refer to the [project homepage](https://mapstruct ## Building from Source -MapStruct uses Maven for its build. Java 17 is required for building MapStruct from source. To build the complete project, run +MapStruct uses Maven for its build. Java 21 is required for building MapStruct from source. +To build the complete project, run ./mvnw clean install